| // Copyright 2023 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #include "src/dawn/native/vulkan/SharedTextureMemoryVk.h" | |
| #include <algorithm> | |
| #include <array> | |
| #include <bit> | |
| #include <utility> | |
| #include "dawn/native/wgpu_structs_autogen.h" | |
| #include "src/dawn/common/Enumerator.h" | |
| #include "src/dawn/common/StackAllocated.h" | |
| #include "src/dawn/common/SystemHandle.h" | |
| #include "src/dawn/native/ChainUtils.h" | |
| #include "src/dawn/native/Instance.h" | |
| #include "src/dawn/native/vulkan/DeviceVk.h" | |
| #include "src/dawn/native/vulkan/PhysicalDeviceVk.h" | |
| #include "src/dawn/native/vulkan/ResourceMemoryAllocatorVk.h" | |
| #include "src/dawn/native/vulkan/SharedFenceVk.h" | |
| #include "src/dawn/native/vulkan/TextureVk.h" | |
| #include "src/dawn/native/vulkan/UtilsVulkan.h" | |
| #include "src/dawn/native/vulkan/VulkanError.h" | |
| #include "src/utils/compiler.h" | |
| #include "src/utils/non_movable.h" | |
| #if DAWN_PLATFORM_IS(ANDROID) | |
| #include <android/hardware_buffer.h> | |
| #include "src/dawn/native/AHBFunctions.h" | |
| #endif // DAWN_PLATFORM_IS(ANDROID) | |
| namespace dawn::native::vulkan { | |
| namespace { | |
| // Encoding from <drm/drm_fourcc.h> | |
| constexpr uint32_t DrmFourccCode(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { | |
| return a | (b << 8) | (c << 16) | (d << 24); | |
| } | |
| constexpr uint32_t DrmFourccCode(char a, char b, char c, char d) { | |
| return DrmFourccCode(static_cast<uint32_t>(a), static_cast<uint32_t>(b), | |
| static_cast<uint32_t>(c), static_cast<uint32_t>(d)); | |
| } | |
| constexpr auto kDrmFormatR8 = DrmFourccCode('R', '8', ' ', ' '); /* [7:0] R */ | |
| constexpr auto kDrmFormatGR88 = | |
| DrmFourccCode('G', 'R', '8', '8'); /* [15:0] G:R 8:8 little endian */ | |
| constexpr auto kDrmFormatXRGB8888 = | |
| DrmFourccCode('X', 'R', '2', '4'); /* [15:0] x:R:G:B 8:8:8:8 little endian */ | |
| constexpr auto kDrmFormatXBGR8888 = | |
| DrmFourccCode('X', 'B', '2', '4'); /* [15:0] x:B:G:R 8:8:8:8 little endian */ | |
| constexpr auto kDrmFormatARGB8888 = | |
| DrmFourccCode('A', 'R', '2', '4'); /* [31:0] A:R:G:B 8:8:8:8 little endian */ | |
| constexpr auto kDrmFormatABGR8888 = | |
| DrmFourccCode('A', 'B', '2', '4'); /* [31:0] A:B:G:R 8:8:8:8 little endian */ | |
| constexpr auto kDrmFormatABGR2101010 = | |
| DrmFourccCode('A', 'B', '3', '0'); /* [31:0] A:B:G:R 2:10:10:10 little endian */ | |
| constexpr auto kDrmFormatABGR16161616F = | |
| DrmFourccCode('A', 'B', '4', 'H'); /* [63:0] A:B:G:R 16:16:16:16 little endian */ | |
| constexpr auto kDrmFormatNV12 = DrmFourccCode('N', 'V', '1', '2'); /* 2x2 subsampled Cr:Cb plane */ | |
| [[maybe_unused]] ResultOrValError<wgpu::TextureFormat> FormatFromDrmFormat(uint32_t drmFormat) { | |
| switch (drmFormat) { | |
| case kDrmFormatR8: | |
| return wgpu::TextureFormat::R8Unorm; | |
| case kDrmFormatGR88: | |
| return wgpu::TextureFormat::RG8Unorm; | |
| case kDrmFormatXRGB8888: | |
| case kDrmFormatARGB8888: | |
| return wgpu::TextureFormat::BGRA8Unorm; | |
| case kDrmFormatXBGR8888: | |
| case kDrmFormatABGR8888: | |
| return wgpu::TextureFormat::RGBA8Unorm; | |
| case kDrmFormatABGR2101010: | |
| return wgpu::TextureFormat::RGB10A2Unorm; | |
| case kDrmFormatABGR16161616F: | |
| return wgpu::TextureFormat::RGBA16Float; | |
| case kDrmFormatNV12: | |
| return wgpu::TextureFormat::R8BG8Biplanar420Unorm; | |
| default: | |
| return DAWN_VALIDATION_ERROR("Unsupported drm format %x.", drmFormat); | |
| } | |
| } | |
| [[maybe_unused]] ResultOrError<uint32_t> FindImportMemoryType( | |
| Device* device, | |
| const VkMemoryRequirements& requirements, | |
| bool allowHostCached) { | |
| auto& allocator = device->GetResourceMemoryAllocator(); | |
| auto index = allocator->FindBestTypeIndex(requirements, MemoryKind::DeviceLocal); | |
| if (!index.has_value() && allowHostCached) { | |
| index = allocator->FindBestTypeIndex(requirements, MemoryKind::HostCached); | |
| } | |
| DAWN_INVALID_IF(!index.has_value(), "Unable to find an appropriate memory type for import."); | |
| return index.value(); | |
| } | |
| struct ViewFormatRequirements { | |
| bool needsMutable; | |
| bool needsBGRA8UnormStoragePolyfill; | |
| }; | |
| [[maybe_unused]] ViewFormatRequirements GetViewFormatRequirements( | |
| Device* device, | |
| const SharedTextureMemoryProperties& properties, | |
| const Format& format) { | |
| constexpr wgpu::TextureUsage kUsageRequiringView = wgpu::TextureUsage::RenderAttachment | | |
| wgpu::TextureUsage::TextureBinding | | |
| wgpu::TextureUsage::StorageBinding; | |
| const bool needsBGRA8UnormStoragePolyfill = | |
| properties.format == wgpu::TextureFormat::BGRA8Unorm && | |
| (properties.usage & wgpu::TextureUsage::StorageBinding) != 0; | |
| const bool viewMayReinterpretFormat = (properties.usage & kUsageRequiringView) != 0 && | |
| !device->GetCompatibleViewFormats(format).empty(); | |
| // Creating per-plane views requires mutable format. | |
| const bool isMultiplanar = format.IsMultiPlanar(); | |
| // DRM modifier tiling alone does not require mutable format. | |
| // When mutable format is needed with DRM modifier tiling, a non-empty | |
| // image format list must be provided by the caller. | |
| // https://docs.vulkan.org/refpages/latest/refpages/source/VkImageCreateInfo.html#VUID-VkImageCreateInfo-tiling-02353 | |
| return { | |
| .needsMutable = needsBGRA8UnormStoragePolyfill || viewMayReinterpretFormat || isMultiplanar, | |
| .needsBGRA8UnormStoragePolyfill = needsBGRA8UnormStoragePolyfill, | |
| }; | |
| } | |
| template <typename Chain> | |
| void AddImageFormatQueryChain(PNextChainBuilder& builder, Chain& chain) { | |
| builder.Add(&chain); | |
| } | |
| // An empty list does not restrict view formats and need not be included in the query. | |
| [[maybe_unused]] void AddImageFormatQueryChain(PNextChainBuilder& builder, | |
| VkImageFormatListCreateInfo& chain) { | |
| if (chain.viewFormatCount > 0) { | |
| builder.Add(&chain); | |
| } | |
| } | |
| // Query import support and validate the image-format limits available before creation. | |
| // Query-only chains are kept local; the caller's creation chain is never modified. | |
| template <typename... AdditionalChains> | |
| MaybeError CheckExternalImageFormatSupport(Device* device, | |
| const SharedTextureMemoryProperties& properties, | |
| const VkImageCreateInfo& createInfo, | |
| AdditionalChains... additionalChains) { | |
| VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = {}; | |
| imageFormatInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2; | |
| imageFormatInfo.format = createInfo.format; | |
| imageFormatInfo.type = createInfo.imageType; | |
| imageFormatInfo.tiling = createInfo.tiling; | |
| imageFormatInfo.usage = createInfo.usage; | |
| imageFormatInfo.flags = createInfo.flags; | |
| PNextChainBuilder imageFormatInfoChain(&imageFormatInfo); | |
| (AddImageFormatQueryChain(imageFormatInfoChain, additionalChains), ...); | |
| VkImageFormatProperties2 imageFormatProps = {}; | |
| imageFormatProps.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2; | |
| VkExternalImageFormatProperties externalImageFormatProps = {}; | |
| PNextChainBuilder imageFormatPropsChain(&imageFormatProps); | |
| imageFormatPropsChain.Add(&externalImageFormatProps, | |
| VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES); | |
| DAWN_TRY_CONTEXT( | |
| CheckVkSuccess(device->fn.GetPhysicalDeviceImageFormatProperties2( | |
| ToBackend(device->GetPhysicalDevice())->GetVkPhysicalDevice(), | |
| &imageFormatInfo, &imageFormatProps), | |
| "vkGetPhysicalDeviceImageFormatProperties2"), | |
| "checking external image import support with %s %s", properties.format, properties.usage); | |
| VkExternalMemoryFeatureFlags featureFlags = | |
| externalImageFormatProps.externalMemoryProperties.externalMemoryFeatures; | |
| DAWN_INVALID_IF(!(featureFlags & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT), | |
| "Vulkan memory is not importable."); | |
| const VkImageFormatProperties& limits = imageFormatProps.imageFormatProperties; | |
| DAWN_INVALID_IF(createInfo.extent.width == 0 || createInfo.extent.height == 0 || | |
| createInfo.extent.depth == 0 || | |
| createInfo.extent.width > limits.maxExtent.width || | |
| createInfo.extent.height > limits.maxExtent.height || | |
| createInfo.extent.depth > limits.maxExtent.depth, | |
| "Image extent (%u, %u, %u) contains zero or exceeds maxExtent (%u, %u, %u).", | |
| createInfo.extent.width, createInfo.extent.height, createInfo.extent.depth, | |
| limits.maxExtent.width, limits.maxExtent.height, limits.maxExtent.depth); | |
| DAWN_INVALID_IF(createInfo.mipLevels == 0 || createInfo.mipLevels > limits.maxMipLevels, | |
| "Image mip level count (%u) is zero or exceeds maxMipLevels (%u).", | |
| createInfo.mipLevels, limits.maxMipLevels); | |
| DAWN_INVALID_IF(createInfo.arrayLayers == 0 || createInfo.arrayLayers > limits.maxArrayLayers, | |
| "Image array layer count (%u) is zero or exceeds maxArrayLayers (%u).", | |
| createInfo.arrayLayers, limits.maxArrayLayers); | |
| DAWN_INVALID_IF(!std::has_single_bit(static_cast<uint32_t>(createInfo.samples)) || | |
| (createInfo.samples & limits.sampleCounts) == 0, | |
| "Image sample count (%u) must be a single bit supported by sampleCounts (%u).", | |
| createInfo.samples, limits.sampleCounts); | |
| // The image size cannot be queried before creation to compare against maxResourceSize. | |
| // Vulkan requires vkCreateImage to fail with VK_ERROR_OUT_OF_DEVICE_MEMORY if that | |
| // limit is exceeded. Rely on that guarantee and propagate OOM during image creation. | |
| // https://docs.vulkan.org/refpages/latest/refpages/source/VkImageFormatProperties.html | |
| return {}; | |
| } | |
| // Import memory with an optional dedicated image. Copy the import structure so no | |
| // caller-owned pNext pointers are modified. | |
| template <typename ImportInfo> | |
| ResultOrError<Ref<RefCountedVkHandle<VkDeviceMemory>>> AllocateDeviceMemory( | |
| Device* device, | |
| VkDeviceSize allocationSize, | |
| VkImage dedicatedImage, | |
| uint32_t memoryTypeIndex, | |
| ImportInfo importInfo) { | |
| VkMemoryAllocateInfo allocateInfo = {}; | |
| allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; | |
| allocateInfo.allocationSize = allocationSize; | |
| allocateInfo.memoryTypeIndex = memoryTypeIndex; | |
| VkMemoryDedicatedAllocateInfo dedicatedInfo = {}; | |
| dedicatedInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO; | |
| dedicatedInfo.image = dedicatedImage; | |
| PNextChainBuilder allocateInfoChain(&allocateInfo); | |
| if (dedicatedImage != VkImage{}) { | |
| allocateInfoChain.Add(&dedicatedInfo); | |
| } | |
| allocateInfoChain.Add(&importInfo); | |
| VkDeviceMemory vkDeviceMemory; | |
| DAWN_TRY(CheckVkOOMThenSuccess( | |
| device->fn.AllocateMemory(device->GetVkDevice(), &allocateInfo, nullptr, &*vkDeviceMemory), | |
| "vkAllocateMemory")); | |
| return AcquireRef(new RefCountedVkHandle<VkDeviceMemory>(device, vkDeviceMemory)); | |
| } | |
| #if DAWN_PLATFORM_IS(POSIX) | |
| ResultOrError<Ref<RefCountedVkHandle<VkDeviceMemory>>> ImportMemoryFD( | |
| Device* device, | |
| int fd, | |
| VkExternalMemoryHandleTypeFlagBits handleType, | |
| VkDeviceSize allocationSize, | |
| uint32_t memoryTypeIndex, | |
| VkImage dedicatedImage = {}) { | |
| SystemHandle memoryFD = SystemHandle::Duplicate(fd); | |
| VkImportMemoryFdInfoKHR importInfo = {}; | |
| importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR; | |
| importInfo.handleType = handleType; | |
| importInfo.fd = memoryFD.Get(); | |
| Ref<RefCountedVkHandle<VkDeviceMemory>> memory; | |
| DAWN_TRY_ASSIGN(memory, AllocateDeviceMemory(device, allocationSize, dedicatedImage, | |
| memoryTypeIndex, importInfo)); | |
| memoryFD.Detach(); // A successful import transfers FD ownership to the Vulkan implementation. | |
| return memory; | |
| } | |
| #endif // DAWN_PLATFORM_IS(POSIX) | |
| } // namespace | |
| // Importers keep their creation chains alive and at stable addresses throughout DoImport. | |
| class SharedTextureMemory::ImageImporter : public StackAllocated, NonMovable { | |
| public: | |
| ImageImporter(Device* device, | |
| VkExternalMemoryHandleTypeFlagBits handleType, | |
| uint32_t queueFamilyIndex) | |
| : device(device), handleType(handleType), mQueueFamilyIndex(queueFamilyIndex) { | |
| externalMemoryImageCreateInfo.handleTypes = handleType; | |
| externalImageFormatInfo.handleType = handleType; | |
| } | |
| virtual ~ImageImporter() = default; | |
| ResultOrError<Ref<SharedTextureMemory>> DoImport(StringView label); | |
| protected: | |
| struct ImportProperties { | |
| SharedTextureMemoryProperties properties = {}; | |
| YCbCrVkDescriptor yCbCrVkDesc = {}; | |
| }; | |
| struct ImportMemoryRequirements { | |
| VkDeviceSize allocationSize; | |
| uint32_t memoryTypeIndex; | |
| }; | |
| virtual ResultOrError<ImportProperties> ValidateDescriptorAndMakeProperties() = 0; | |
| virtual MaybeError ValidateAndDescribeViewFormats() = 0; | |
| virtual VkImageCreateInfo MakeImageCreateInfo() = 0; | |
| virtual MaybeError CheckSupport(const VkImageCreateInfo& createInfo) { | |
| return CheckExternalImageFormatSupport(device, GetProperties(), createInfo, | |
| externalImageFormatInfo, imageFormatListInfo); | |
| } | |
| virtual ResultOrError<ImportMemoryRequirements> GatherMemoryRequirements(VkImage image) = 0; | |
| virtual ResultOrError<Ref<RefCountedVkHandle<VkDeviceMemory>>> ImportMemory( | |
| VkImage image, | |
| const ImportMemoryRequirements& requirements) = 0; | |
| virtual MaybeError ValidateAfterBind(VkImage image) { return {}; } | |
| // Only DoImport can update the properties, before the remaining import steps read them. | |
| const SharedTextureMemoryProperties& GetProperties() const { return mProperties; } | |
| template <typename... AdditionalChains> | |
| VkImageCreateInfo MakeImageCreateInfo(VkImageTiling tiling, | |
| AdditionalChains&... additionalChains) { | |
| const auto& properties = GetProperties(); | |
| VkImageCreateInfo createInfo = {}; | |
| createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; | |
| createInfo.flags = VulkanImageCreateFlags(device, properties.usage, *internalFormat, | |
| /*sampleCount=*/1) | | |
| extraImageFlags; | |
| createInfo.imageType = VK_IMAGE_TYPE_2D; | |
| createInfo.format = vkFormat; | |
| createInfo.extent = {properties.size.width, properties.size.height, 1}; | |
| createInfo.mipLevels = 1; | |
| createInfo.arrayLayers = properties.size.depthOrArrayLayers; | |
| createInfo.samples = VK_SAMPLE_COUNT_1_BIT; | |
| createInfo.tiling = tiling; | |
| createInfo.usage = VulkanImageUsage(device, properties.usage, *internalFormat); | |
| createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
| createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
| PNextChainBuilder chain(&createInfo); | |
| chain.Add(&externalMemoryImageCreateInfo); | |
| chain.Add(&imageFormatListInfo); | |
| (chain.Add(&additionalChains), ...); | |
| return createInfo; | |
| } | |
| Device* const device; | |
| const VkExternalMemoryHandleTypeFlagBits handleType; | |
| const Format* internalFormat = nullptr; | |
| VkFormat vkFormat = VK_FORMAT_UNDEFINED; | |
| VkImageCreateFlags extraImageFlags = 0; | |
| std::array<VkFormat, 3> viewFormats = {}; | |
| VkImageFormatListCreateInfo imageFormatListInfo = { | |
| .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, | |
| }; | |
| VkExternalMemoryImageCreateInfo externalMemoryImageCreateInfo = { | |
| .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, | |
| }; | |
| VkPhysicalDeviceExternalImageFormatInfo externalImageFormatInfo = { | |
| .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO, | |
| }; | |
| private: | |
| const uint32_t mQueueFamilyIndex; | |
| SharedTextureMemoryProperties mProperties = {}; | |
| }; | |
| #if DAWN_PLATFORM_IS(LINUX) | |
| struct SharedTextureMemory::DmaBufImporter final : ImageImporter { | |
| DmaBufImporter(Device* deviceIn, const SharedTextureMemoryDmaBufDescriptor* descriptor) | |
| : ImageImporter(deviceIn, | |
| VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, | |
| VK_QUEUE_FAMILY_EXTERNAL_KHR), | |
| descriptor(descriptor) {} | |
| ResultOrError<ImportProperties> ValidateDescriptorAndMakeProperties() override { | |
| const CombinedLimits& limits = device->GetLimits(); | |
| DAWN_INVALID_IF( | |
| descriptor->size.width == 0 || descriptor->size.width > limits.v1.maxTextureDimension2D, | |
| "Resource width (%u) is zero or exceeds maxTextureDimension2D (%u).", | |
| descriptor->size.width, limits.v1.maxTextureDimension2D); | |
| DAWN_INVALID_IF(descriptor->size.height == 0 || | |
| descriptor->size.height > limits.v1.maxTextureDimension2D, | |
| "Resource height (%u) is zero or exceeds maxTextureDimension2D (%u).", | |
| descriptor->size.height, limits.v1.maxTextureDimension2D); | |
| DAWN_INVALID_IF(descriptor->size.depthOrArrayLayers != 1, "depthOrArrayLayers was not 1."); | |
| SharedTextureMemoryProperties properties = {}; | |
| properties.size = {descriptor->size.width, descriptor->size.height, | |
| descriptor->size.depthOrArrayLayers}; | |
| DAWN_TRY_ASSIGN(properties.format, FormatFromDrmFormat(descriptor->drmFormat)); | |
| properties.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst | | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding | | |
| wgpu::TextureUsage::RenderAttachment; | |
| DAWN_TRY_ASSIGN(internalFormat, device->GetInternalFormat(properties.format)); | |
| vkFormat = VulkanImageFormat(device, properties.format); | |
| VkPhysicalDevice vkPhysicalDevice = | |
| ToBackend(device->GetPhysicalDevice())->GetVkPhysicalDevice(); | |
| // Verify plane count for the modifier. | |
| // https://docs.vulkan.org/refpages/latest/refpages/source/VkDrmFormatModifierPropertiesEXT.html#_description | |
| VkDrmFormatModifierPropertiesEXT drmModifierProps; | |
| DAWN_TRY_ASSIGN(drmModifierProps, | |
| GetFormatModifierProps(device->fn, vkPhysicalDevice, vkFormat, | |
| descriptor->drmModifier)); | |
| memoryPlaneCount = drmModifierProps.drmFormatModifierPlaneCount; | |
| if (drmModifierProps.drmFormatModifier == 0 /* DRM_FORMAT_MOD_LINEAR */) { | |
| uint32_t formatPlaneCount = GetAspectCount(internalFormat->aspects); | |
| DAWN_INVALID_IF(memoryPlaneCount != formatPlaneCount, | |
| "DRM format plane count (%u) must match the format plane count (%u) if " | |
| "drmModifier is DRM_FORMAT_MOD_LINEAR.", | |
| memoryPlaneCount, formatPlaneCount); | |
| } | |
| DAWN_INVALID_IF( | |
| memoryPlaneCount != descriptor->planes.size(), | |
| "Memory plane count (%x) for drm format (%u) and modifier (%u) specify a plane " | |
| "count of %u which " | |
| "does not match the provided plane count (%u)", | |
| vkFormat, descriptor->drmFormat, descriptor->drmModifier, memoryPlaneCount, | |
| descriptor->planes.size()); | |
| DAWN_INVALID_IF(memoryPlaneCount == 0, "Memory plane count must not be 0"); | |
| DAWN_INVALID_IF(memoryPlaneCount > kMaxPlanesPerFormat, | |
| "Memory plane count (%u) must not exceed %u.", memoryPlaneCount, | |
| kMaxPlanesPerFormat); | |
| // Validate that there is a single FD. If there is more than one FD, Dawn will need to | |
| // validate the format has VK_FORMAT_FEATURE_DISJOINT_BIT, create the VkImage with | |
| // VK_IMAGE_CREATE_DISJOINT_BIT, and separately bind the image planes to memory. Dawn | |
| // doesn't support use of VK_IMAGE_CREATE_DISJOINT_BIT currently. See crbug.com/42240514. | |
| fd = descriptor->planes[0].fd; | |
| for (auto [i, plane] : Enumerate(descriptor->planes.subspan(1))) { | |
| DAWN_INVALID_IF( | |
| plane.fd != fd, | |
| "descriptor->planes[%u].fd (%i) does not match other plane fd (%i). All " | |
| "fds must be the same.", | |
| i + 1, plane.fd, fd); | |
| } | |
| return ImportProperties{properties}; | |
| } | |
| MaybeError ValidateAndDescribeViewFormats() override { | |
| const auto& properties = GetProperties(); | |
| const auto& compatibleViewFormats = device->GetCompatibleViewFormats(*internalFormat); | |
| const auto viewRequirements = | |
| GetViewFormatRequirements(device, properties, *internalFormat); | |
| if (viewRequirements.needsMutable) { | |
| // Allow format reinterpretation and per-plane views. | |
| extraImageFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; | |
| // Append the list of view formats the image must be compatible with. | |
| if (device->GetDeviceInfo().HasExt(DeviceExt::ImageFormatList)) { | |
| if (internalFormat->IsMultiPlanar()) { | |
| viewFormats = { | |
| VulkanImageFormat(device, | |
| internalFormat->GetAspectInfo(Aspect::Plane0).format), | |
| VulkanImageFormat(device, | |
| internalFormat->GetAspectInfo(Aspect::Plane1).format)}; | |
| imageFormatListInfo.viewFormatCount = 2; | |
| } else { | |
| // Start with the base format and add the BGRA storage polyfill format if | |
| // needed. Mutable images with DRM modifier tiling require a non-empty list. | |
| const bool needsBGRA8UnormStoragePolyfill = | |
| viewRequirements.needsBGRA8UnormStoragePolyfill; | |
| viewFormats[imageFormatListInfo.viewFormatCount++] = vkFormat; | |
| DAWN_ASSERT(!compatibleViewFormats.empty() || !needsBGRA8UnormStoragePolyfill); | |
| if (needsBGRA8UnormStoragePolyfill) { | |
| viewFormats[imageFormatListInfo.viewFormatCount++] = | |
| VK_FORMAT_R8G8B8A8_UNORM; | |
| } | |
| } | |
| imageFormatListInfo.pViewFormats = viewFormats.data(); | |
| } | |
| } | |
| // The support query excludes the sRGB view format for storage compatibility. | |
| // Image creation keeps it when backend validation is disabled; otherwise, omit it to | |
| // avoid the existing Vulkan validation layer errors for this combination. | |
| // See https://github.com/gpuweb/gpuweb/issues/4426. | |
| // TODO(crbug.com/dawn/2304): Follow up with the Vulkan spec and lift this workaround. | |
| queryViewFormatCount = imageFormatListInfo.viewFormatCount; | |
| if (!internalFormat->IsMultiPlanar() && queryViewFormatCount > 0 && | |
| !compatibleViewFormats.empty() && | |
| !device->GetAdapter()->GetInstance()->IsBackendValidationEnabled()) { | |
| DAWN_ASSERT(compatibleViewFormats.size() == 1u); | |
| viewFormats[imageFormatListInfo.viewFormatCount++] = | |
| VulkanImageFormat(device, compatibleViewFormats[0]->format); | |
| } | |
| return {}; | |
| } | |
| VkImageCreateInfo MakeImageCreateInfo() override { | |
| // Zero initialization supplies the required zero size and unused array/depth pitches. | |
| for (uint32_t plane = 0; plane < memoryPlaneCount; ++plane) { | |
| planeLayouts[plane].offset = descriptor->planes[plane].offset; | |
| planeLayouts[plane].rowPitch = descriptor->planes[plane].stride; | |
| } | |
| explicitCreateInfo.drmFormatModifier = descriptor->drmModifier; | |
| explicitCreateInfo.drmFormatModifierPlaneCount = memoryPlaneCount; | |
| explicitCreateInfo.pPlaneLayouts = planeLayouts.data(); | |
| return ImageImporter::MakeImageCreateInfo(VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, | |
| explicitCreateInfo); | |
| } | |
| MaybeError CheckSupport(const VkImageCreateInfo& createInfo) override { | |
| VkPhysicalDeviceImageDrmFormatModifierInfoEXT drmModifierInfo = {}; | |
| drmModifierInfo.sType = | |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT; | |
| drmModifierInfo.drmFormatModifier = descriptor->drmModifier; | |
| drmModifierInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
| // Use the shorter format list for the query, preserving the creation list's count. | |
| auto queryFormatList = imageFormatListInfo; | |
| queryFormatList.viewFormatCount = queryViewFormatCount; | |
| return CheckExternalImageFormatSupport(device, GetProperties(), createInfo, | |
| externalImageFormatInfo, queryFormatList, | |
| drmModifierInfo); | |
| } | |
| ResultOrError<ImportMemoryRequirements> GatherMemoryRequirements(VkImage image) override { | |
| VkDevice vkDevice = device->GetVkDevice(); | |
| VkMemoryFdPropertiesKHR fdProperties; | |
| fdProperties.sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR; | |
| fdProperties.pNext = nullptr; | |
| // Get the valid memory types that the external memory can be imported as. | |
| DAWN_TRY(CheckVkSuccess( | |
| device->fn.GetMemoryFdPropertiesKHR(vkDevice, handleType, fd, &fdProperties), | |
| "vkGetMemoryFdPropertiesKHR")); | |
| // Get the valid memory types for the VkImage. | |
| VkMemoryRequirements memoryRequirements; | |
| device->fn.GetImageMemoryRequirements(vkDevice, image, &memoryRequirements); | |
| // Choose the best memory type that satisfies both the image's constraint and the | |
| // import's constraint. | |
| memoryRequirements.memoryTypeBits &= fdProperties.memoryTypeBits; | |
| // Some dma-buf imports may lack a compatible device-local memory type. Allow a host-cached | |
| // fallback for this case, observed on AMD with imports thought to originate from a camera. | |
| // See crbug.com/422128949. | |
| uint32_t memoryTypeIndex; | |
| DAWN_TRY_ASSIGN(memoryTypeIndex, | |
| FindImportMemoryType(device, memoryRequirements, /*allowHostCached=*/true)); | |
| return ImportMemoryRequirements{memoryRequirements.size, memoryTypeIndex}; | |
| } | |
| ResultOrError<Ref<RefCountedVkHandle<VkDeviceMemory>>> ImportMemory( | |
| VkImage image, | |
| const ImportMemoryRequirements& requirements) override { | |
| return ImportMemoryFD(device, fd, handleType, requirements.allocationSize, | |
| requirements.memoryTypeIndex); | |
| } | |
| const SharedTextureMemoryDmaBufDescriptor* const descriptor; | |
| uint32_t memoryPlaneCount = 0; | |
| uint32_t queryViewFormatCount = 0; | |
| int fd = -1; | |
| // Keep these as members so the returned VkImageCreateInfo points to stable chain data. | |
| std::array<VkSubresourceLayout, kMaxPlanesPerFormat> planeLayouts = {}; | |
| VkImageDrmFormatModifierExplicitCreateInfoEXT explicitCreateInfo = { | |
| .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT, | |
| }; | |
| }; | |
| #endif // DAWN_PLATFORM_IS(LINUX) | |
| #if DAWN_PLATFORM_IS(ANDROID) | |
| struct SharedTextureMemory::AHardwareBufferImporter final : ImageImporter { | |
| AHardwareBufferImporter(Device* deviceIn, | |
| const SharedTextureMemoryAHardwareBufferDescriptor* descriptor) | |
| : ImageImporter(deviceIn, | |
| VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID, | |
| VK_QUEUE_FAMILY_FOREIGN_EXT), | |
| aHardwareBuffer(static_cast<struct AHardwareBuffer*>(descriptor->handle)) {} | |
| ResultOrError<ImportProperties> ValidateDescriptorAndMakeProperties() override { | |
| const auto* ahbFunctions = | |
| ToBackend(device->GetAdapter()->GetPhysicalDevice())->GetOrLoadAHBFunctions(); | |
| VkDevice vkDevice = device->GetVkDevice(); | |
| // Reflect the properties of the AHardwareBuffer. | |
| AHBSharedTextureMemoryProperties ahbProperties = | |
| GetAHBSharedTextureMemoryProperties(ahbFunctions, aHardwareBuffer); | |
| // Reject protected AHBs. | |
| // Dawn doesn't enable VkPhysicalDeviceProtectedMemoryFeatures::protectedMemory and never | |
| // sets VK_IMAGE_CREATE_PROTECTED_BIT, so an AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT buffer | |
| // would produce an invalid vkAllocateMemory (VUID-VkMemoryAllocateInfo-None-01872) and | |
| // vkBindImageMemory (VUID-vkBindImageMemory-None-01902). | |
| DAWN_INVALID_IF( | |
| ahbProperties.isProtected, | |
| "Unsupported AHardwareBuffer usage AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT."); | |
| SharedTextureMemoryProperties properties = ahbProperties.properties; | |
| YCbCrVkDescriptor yCbCrVkDesc = {}; | |
| const CombinedLimits& limits = device->GetLimits(); | |
| DAWN_INVALID_IF(properties.size.width > limits.v1.maxTextureDimension2D, | |
| "Resource width (%u) exceeds maxTextureDimension2D (%u).", | |
| properties.size.width, limits.v1.maxTextureDimension2D); | |
| DAWN_INVALID_IF(properties.size.height > limits.v1.maxTextureDimension2D, | |
| "Resource weight (%u) exceeds maxTextureDimension2D (%u).", | |
| properties.size.height, limits.v1.maxTextureDimension2D); | |
| DAWN_INVALID_IF(properties.size.depthOrArrayLayers > limits.v1.maxTextureArrayLayers, | |
| "Resource layers (%d) exceeds maxTextureArrayLayers (%u).", | |
| properties.size.depthOrArrayLayers, limits.v1.maxTextureArrayLayers); | |
| usesExternalFormat = properties.format == wgpu::TextureFormat::OpaqueYCbCrAndroid; | |
| if (usesExternalFormat) { | |
| // Multi-layer YCbCr AHBs shouldn't be possible to create but validate it just in case. | |
| DAWN_INVALID_IF(properties.size.depthOrArrayLayers != 1, | |
| "Resource layers (%d) is not 1 for YCbCr formats.", | |
| properties.size.depthOrArrayLayers); | |
| // When using the opaque YUV texture formats, only the TextureBinding usage is valid. | |
| properties.usage &= wgpu::TextureUsage::TextureBinding; | |
| } | |
| // Query the properties to find the appropriate VkFormat and memory type. | |
| { | |
| VkAndroidHardwareBufferFormatPropertiesANDROID bufferFormatProperties; | |
| PNextChainBuilder bufferPropertiesChain(&bufferProperties); | |
| bufferPropertiesChain.Add( | |
| &bufferFormatProperties, | |
| VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID); | |
| DAWN_TRY(CheckVkSuccess(device->fn.GetAndroidHardwareBufferPropertiesANDROID( | |
| vkDevice, aHardwareBuffer, &bufferProperties), | |
| "vkGetAndroidHardwareBufferPropertiesANDROID")); | |
| // TODO(crbug.com/dawn/2476): Validate more as per | |
| // https://docs.vulkan.org/refpages/latest/refpages/source/VkImageCreateInfo.html | |
| if (usesExternalFormat) { | |
| DAWN_INVALID_IF( | |
| bufferFormatProperties.externalFormat == 0, | |
| "AHardwareBuffer with external sampler must have non-zero external format."); | |
| vkFormat = VK_FORMAT_UNDEFINED; | |
| externalFormatAndroid.externalFormat = bufferFormatProperties.externalFormat; | |
| } else { | |
| vkFormat = bufferFormatProperties.format; | |
| DAWN_TRY_ASSIGN(properties.format, FormatFromVkFormat(device, vkFormat)); | |
| } | |
| // Populate the YCbCr info. | |
| yCbCrVkDesc.externalFormat = externalFormatAndroid.externalFormat; | |
| yCbCrVkDesc.vkFormat = vkFormat; | |
| yCbCrVkDesc.vkYCbCrModel = bufferFormatProperties.suggestedYcbcrModel; | |
| yCbCrVkDesc.vkYCbCrRange = bufferFormatProperties.suggestedYcbcrRange; | |
| yCbCrVkDesc.vkComponentSwizzleRed = | |
| bufferFormatProperties.samplerYcbcrConversionComponents.r; | |
| yCbCrVkDesc.vkComponentSwizzleGreen = | |
| bufferFormatProperties.samplerYcbcrConversionComponents.g; | |
| yCbCrVkDesc.vkComponentSwizzleBlue = | |
| bufferFormatProperties.samplerYcbcrConversionComponents.b; | |
| yCbCrVkDesc.vkComponentSwizzleAlpha = | |
| bufferFormatProperties.samplerYcbcrConversionComponents.a; | |
| yCbCrVkDesc.vkXChromaOffset = bufferFormatProperties.suggestedXChromaOffset; | |
| yCbCrVkDesc.vkYChromaOffset = bufferFormatProperties.suggestedYChromaOffset; | |
| uint32_t formatFeatures = bufferFormatProperties.formatFeatures; | |
| if (formatFeatures & | |
| VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT) { | |
| yCbCrVkDesc.vkChromaFilter = wgpu::FilterMode::Linear; | |
| } else { | |
| yCbCrVkDesc.vkChromaFilter = wgpu::FilterMode::Nearest; | |
| } | |
| yCbCrVkDesc.forceExplicitReconstruction = | |
| formatFeatures & | |
| VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT; | |
| } | |
| // Clear the chain pointer to the local bufferFormatProperties, which is now out of scope. | |
| bufferProperties.pNext = nullptr; | |
| DAWN_TRY_ASSIGN(internalFormat, device->GetInternalFormat(properties.format)); | |
| DAWN_INVALID_IF(internalFormat->IsMultiPlanar(), | |
| "Multi-planar AHardwareBuffer not supported yet."); | |
| return ImportProperties{properties, yCbCrVkDesc}; | |
| } | |
| MaybeError ValidateAndDescribeViewFormats() override { | |
| const auto& properties = GetProperties(); | |
| if (usesExternalFormat) { | |
| return {}; | |
| } | |
| const auto& compatibleViewFormats = device->GetCompatibleViewFormats(*internalFormat); | |
| const auto viewRequirements = | |
| GetViewFormatRequirements(device, properties, *internalFormat); | |
| if (viewRequirements.needsMutable) { | |
| // Allow format reinterpretation. | |
| extraImageFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; | |
| // Leave the view-format list empty for storage usage to avoid querying sRGB view | |
| // formats with unsupported storage usage. The empty list is omitted from the query | |
| // but is still chained during image creation. Mutable images with DRM modifier | |
| // tiling require a non-empty list, so the dma-buf path uses a different workaround. | |
| // TODO(crbug.com/dawn/2304): Apply a common fix when the format-list issue is resolved. | |
| if ((properties.usage & wgpu::TextureUsage::StorageBinding) == 0 && | |
| device->GetDeviceInfo().HasExt(DeviceExt::ImageFormatList)) { | |
| // Set the list of view formats the image can be compatible with. | |
| DAWN_ASSERT(compatibleViewFormats.size() == 1u); | |
| viewFormats[0] = vkFormat; | |
| viewFormats[1] = VulkanImageFormat(device, compatibleViewFormats[0]->format); | |
| imageFormatListInfo.viewFormatCount = 2; | |
| imageFormatListInfo.pViewFormats = viewFormats.data(); | |
| } | |
| } | |
| return {}; | |
| } | |
| VkImageCreateInfo MakeImageCreateInfo() override { | |
| return ImageImporter::MakeImageCreateInfo(VK_IMAGE_TILING_OPTIMAL, externalFormatAndroid); | |
| } | |
| MaybeError CheckSupport(const VkImageCreateInfo& createInfo) override { | |
| // External Android formats use VK_FORMAT_UNDEFINED and cannot use this format query. | |
| if (usesExternalFormat) { | |
| return {}; | |
| } | |
| return ImageImporter::CheckSupport(createInfo); | |
| } | |
| ResultOrError<ImportMemoryRequirements> GatherMemoryRequirements(VkImage image) override { | |
| // AHB images cannot be queried until bound. Use the buffer's requirements for import. | |
| VkMemoryRequirements requirements = {}; | |
| requirements.memoryTypeBits = bufferProperties.memoryTypeBits; | |
| uint32_t memoryTypeIndex; | |
| DAWN_TRY_ASSIGN(memoryTypeIndex, | |
| FindImportMemoryType(device, requirements, /*allowHostCached=*/false)); | |
| return ImportMemoryRequirements{bufferProperties.allocationSize, memoryTypeIndex}; | |
| } | |
| ResultOrError<Ref<RefCountedVkHandle<VkDeviceMemory>>> ImportMemory( | |
| VkImage image, | |
| const ImportMemoryRequirements& requirements) override { | |
| VkImportAndroidHardwareBufferInfoANDROID importInfo = {}; | |
| importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID; | |
| importInfo.buffer = aHardwareBuffer; | |
| // AHardwareBuffer image imports must use dedicated allocations. | |
| // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#memory-external-android-hardware-buffer-image-resources | |
| return AllocateDeviceMemory(device, requirements.allocationSize, image, | |
| requirements.memoryTypeIndex, importInfo); | |
| } | |
| MaybeError ValidateAfterBind(VkImage image) override { | |
| VkMemoryRequirements memoryRequirements; | |
| // Unlike ordinary images, AHB images must be bound before querying memory requirements | |
| // (VUID-vkGetImageMemoryRequirements-image-04004). Verify they fit the AHB constraints. | |
| // https://docs.vulkan.org/refpages/latest/refpages/source/vkGetImageMemoryRequirements.html#VUID-vkGetImageMemoryRequirements-image-04004 | |
| device->fn.GetImageMemoryRequirements(device->GetVkDevice(), image, &memoryRequirements); | |
| DAWN_INVALID_IF((memoryRequirements.memoryTypeBits & bufferProperties.memoryTypeBits) == 0, | |
| "Required memory type bits (%u) do not overlap with AHardwareBuffer memory " | |
| "type bits (%u).", | |
| memoryRequirements.memoryTypeBits, bufferProperties.memoryTypeBits); | |
| if (!device->IsToggleEnabled(Toggle::IgnoreImportedAHardwareBufferVulkanImageSize)) { | |
| DAWN_INVALID_IF(memoryRequirements.size > bufferProperties.allocationSize, | |
| "Required texture memory size (%u) is larger than the AHardwareBuffer " | |
| "allocation size (%u).", | |
| memoryRequirements.size, bufferProperties.allocationSize); | |
| } | |
| return {}; | |
| } | |
| ::AHardwareBuffer* const aHardwareBuffer; | |
| bool usesExternalFormat = false; | |
| VkAndroidHardwareBufferPropertiesANDROID bufferProperties = { | |
| .sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID, | |
| }; | |
| VkExternalFormatANDROID externalFormatAndroid = { | |
| .sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID, | |
| }; | |
| }; | |
| #endif // DAWN_PLATFORM_IS(ANDROID) | |
| #if DAWN_PLATFORM_IS(POSIX) | |
| struct SharedTextureMemory::OpaqueFDImporter final : ImageImporter { | |
| OpaqueFDImporter(Device* deviceIn, const SharedTextureMemoryOpaqueFDDescriptor* descriptor) | |
| : ImageImporter(deviceIn, | |
| VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT, | |
| VK_QUEUE_FAMILY_EXTERNAL_KHR), | |
| descriptor(descriptor), | |
| createInfo(static_cast<const VkImageCreateInfo*>(descriptor->vkImageCreateInfo)) {} | |
| ResultOrError<ImportProperties> ValidateDescriptorAndMakeProperties() override { | |
| imageFormatListInfo = {}; | |
| DAWN_INVALID_IF( | |
| createInfo->sType != VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, | |
| "descriptor->vkImageCreateInfo.sType was not VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO."); | |
| // Validate the createInfo chain. | |
| const VkExternalMemoryImageCreateInfo* externalMemoryImageCreateInfo = nullptr; | |
| { | |
| const VkBaseInStructure* current = | |
| static_cast<const VkBaseInStructure*>(createInfo->pNext); | |
| while (current != nullptr) { | |
| switch (current->sType) { | |
| case VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO: | |
| // TODO(crbug.com/dawn/1745): Use this to inform supported types of WebGPU | |
| // format reinterpretation (srgb). | |
| imageFormatListInfo = | |
| *reinterpret_cast<const VkImageFormatListCreateInfo*>(current); | |
| break; | |
| case VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO: | |
| externalMemoryImageCreateInfo = | |
| reinterpret_cast<const VkExternalMemoryImageCreateInfo*>(current); | |
| DAWN_INVALID_IF( | |
| (externalMemoryImageCreateInfo->handleTypes & handleType) == 0, | |
| "VkExternalMemoryImageCreateInfo::handleTypes (chained on " | |
| "descriptor->vkImageCreateInfo) did not have " | |
| "VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT."); | |
| break; | |
| default: | |
| return DAWN_VALIDATION_ERROR( | |
| "Unsupported descriptor->vkImageCreateInfo chain with sType 0x%x", | |
| current->sType); | |
| } | |
| current = current->pNext; | |
| } | |
| } | |
| DAWN_INVALID_IF(externalMemoryImageCreateInfo == nullptr, | |
| "descriptor->vkImageCreateInfo did not have chain with " | |
| "VkExternalMemoryImageCreateInfo"); | |
| DAWN_INVALID_IF( | |
| (createInfo->usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) == 0, | |
| "descriptor->vkImageCreateInfo.usage did not have VK_IMAGE_USAGE_TRANSFER_DST_BIT"); | |
| // Populate the properties from the VkImageCreateInfo | |
| SharedTextureMemoryProperties properties = {}; | |
| properties.size = { | |
| createInfo->extent.width, | |
| createInfo->extent.height, | |
| std::max(createInfo->arrayLayers, createInfo->extent.depth), | |
| }; | |
| DAWN_TRY_ASSIGN(properties.format, FormatFromVkFormat(device, createInfo->format)); | |
| if (createInfo->usage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) { | |
| properties.usage |= wgpu::TextureUsage::CopySrc; | |
| } | |
| if (createInfo->usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT) { | |
| properties.usage |= wgpu::TextureUsage::CopyDst; | |
| } | |
| if (createInfo->usage & VK_IMAGE_USAGE_SAMPLED_BIT) { | |
| properties.usage |= wgpu::TextureUsage::TextureBinding; | |
| } | |
| if (createInfo->usage & VK_IMAGE_USAGE_STORAGE_BIT) { | |
| properties.usage |= wgpu::TextureUsage::StorageBinding; | |
| } | |
| if (createInfo->usage & | |
| (VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) { | |
| properties.usage |= wgpu::TextureUsage::RenderAttachment; | |
| } | |
| if (createInfo->usage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT) { | |
| properties.usage |= wgpu::TextureUsage::TransientAttachment; | |
| } | |
| DAWN_TRY_ASSIGN(internalFormat, device->GetInternalFormat(properties.format)); | |
| return ImportProperties{properties}; | |
| } | |
| MaybeError ValidateAndDescribeViewFormats() override { | |
| const auto& properties = GetProperties(); | |
| const auto& compatibleViewFormats = device->GetCompatibleViewFormats(*internalFormat); | |
| const bool isBGRA8UnormStorage = createInfo->format == VK_FORMAT_B8G8R8A8_UNORM && | |
| (createInfo->usage & VK_IMAGE_USAGE_STORAGE_BIT) != 0; | |
| DAWN_INVALID_IF( | |
| isBGRA8UnormStorage && (createInfo->flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) == 0, | |
| "descriptor->vkImageCreateInfo.flags did not have VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT " | |
| "when " | |
| "usage has VK_IMAGE_USAGE_STORAGE_BIT and format is VK_FORMAT_B8G8R8A8_UNORM"); | |
| const bool needsMutable = | |
| GetViewFormatRequirements(device, properties, *internalFormat).needsMutable; | |
| DAWN_INVALID_IF(needsMutable && !(createInfo->flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT), | |
| "VkImageCreateInfo::flags did not have VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT " | |
| "which is required for view format reinterpretation or per-plane views."); | |
| if (imageFormatListInfo.sType == VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO && | |
| needsMutable && !compatibleViewFormats.empty()) { | |
| // SAFETY: Vulkan requires that pViewFormats point at viewFormatCount valid formats and | |
| // the application giving us its VkImageCreateInfo needs to conform to that. | |
| Span<const VkFormat> viewFormats = DAWN_UNSAFE_BUFFERS( | |
| {imageFormatListInfo.pViewFormats, imageFormatListInfo.viewFormatCount}); | |
| VkFormat baseVkFormat = VulkanImageFormat(device, properties.format); | |
| DAWN_INVALID_IF( | |
| std::ranges::find(viewFormats, baseVkFormat) == viewFormats.end(), | |
| "VkImageFormatCreateInfo did not contain VkFormat 0x%x which may be required to " | |
| "create a texture view with %s.", | |
| baseVkFormat, properties.format); | |
| for (const auto* f : compatibleViewFormats) { | |
| VkFormat vkFormat = VulkanImageFormat(device, f->format); | |
| DAWN_INVALID_IF( | |
| std::ranges::find(viewFormats, vkFormat) == viewFormats.end(), | |
| "VkImageFormatCreateInfo did not contain VkFormat 0x%x which may be " | |
| "required to create a texture view with %s.", | |
| vkFormat, f->format); | |
| } | |
| } | |
| return {}; | |
| } | |
| VkImageCreateInfo MakeImageCreateInfo() override { | |
| // Keep the application's image parameters and pNext chain unchanged. | |
| return *createInfo; | |
| } | |
| ResultOrError<ImportMemoryRequirements> GatherMemoryRequirements(VkImage image) override { | |
| VkMemoryRequirements requirements; | |
| device->fn.GetImageMemoryRequirements(device->GetVkDevice(), image, &requirements); | |
| DAWN_INVALID_IF(requirements.size > descriptor->allocationSize, | |
| "Required texture memory size (%u) is larger than the memory fd " | |
| "allocation size (%u).", | |
| requirements.size, descriptor->allocationSize); | |
| return ImportMemoryRequirements{descriptor->allocationSize, descriptor->memoryTypeIndex}; | |
| } | |
| ResultOrError<Ref<RefCountedVkHandle<VkDeviceMemory>>> ImportMemory( | |
| VkImage image, | |
| const ImportMemoryRequirements& requirements) override { | |
| return ImportMemoryFD(device, descriptor->memoryFD, handleType, requirements.allocationSize, | |
| requirements.memoryTypeIndex, | |
| descriptor->dedicatedAllocation ? image : VkImage{}); | |
| } | |
| const SharedTextureMemoryOpaqueFDDescriptor* const descriptor; | |
| const VkImageCreateInfo* const createInfo; | |
| }; | |
| #endif // DAWN_PLATFORM_IS(POSIX) | |
| // Keep the import sequence here so each handle type validates and acquires resources in the | |
| // same order. The STM owns each resource as soon as it is created, including on failure. | |
| ResultOrError<Ref<SharedTextureMemory>> SharedTextureMemory::ImageImporter::DoImport( | |
| StringView label) { | |
| ImportProperties importProperties; | |
| DAWN_TRY_ASSIGN(importProperties, ValidateDescriptorAndMakeProperties()); | |
| Ref<SharedTextureMemory> sharedTextureMemory = | |
| AcquireRef(new SharedTextureMemory(device, label, importProperties.properties, | |
| mQueueFamilyIndex, importProperties.yCbCrVkDesc)); | |
| sharedTextureMemory->Initialize(); | |
| // Store the reified properties before the remaining steps read them through GetProperties(). | |
| DAWN_TRY(sharedTextureMemory->GetProperties(&mProperties)); | |
| DAWN_TRY(ValidateAndDescribeViewFormats()); | |
| VkImageCreateInfo createInfo = MakeImageCreateInfo(); | |
| DAWN_TRY(CheckSupport(createInfo)); | |
| VkImage image; | |
| DAWN_TRY(CheckVkOOMThenSuccess( | |
| device->fn.CreateImage(device->GetVkDevice(), &createInfo, nullptr, &*image), | |
| "vkCreateImage")); | |
| sharedTextureMemory->mVkImage = AcquireRef(new RefCountedVkHandle<VkImage>(device, image)); | |
| ImportMemoryRequirements requirements = {}; | |
| DAWN_TRY_ASSIGN(requirements, GatherMemoryRequirements(image)); | |
| DAWN_TRY_ASSIGN(sharedTextureMemory->mVkDeviceMemory, ImportMemory(image, requirements)); | |
| DAWN_TRY(CheckVkOOMThenSuccess( | |
| device->fn.BindImageMemory(device->GetVkDevice(), image, | |
| sharedTextureMemory->mVkDeviceMemory->Get(), 0), | |
| "vkBindImageMemory")); | |
| DAWN_TRY(ValidateAfterBind(image)); | |
| return sharedTextureMemory; | |
| } | |
| // static | |
| ResultOrError<Ref<SharedTextureMemory>> SharedTextureMemory::Create( | |
| Device* device, | |
| StringView label, | |
| const SharedTextureMemoryDmaBufDescriptor* descriptor) { | |
| #if DAWN_PLATFORM_IS(LINUX) | |
| DmaBufImporter importer(device, descriptor); | |
| return importer.DoImport(label); | |
| #else | |
| DAWN_UNREACHABLE(); | |
| #endif // DAWN_PLATFORM_IS(LINUX) | |
| } | |
| // static | |
| ResultOrError<Ref<SharedTextureMemory>> SharedTextureMemory::Create( | |
| Device* device, | |
| StringView label, | |
| const SharedTextureMemoryAHardwareBufferDescriptor* descriptor) { | |
| #if DAWN_PLATFORM_IS(ANDROID) | |
| AHardwareBufferImporter importer(device, descriptor); | |
| return importer.DoImport(label); | |
| #else | |
| DAWN_UNREACHABLE(); | |
| #endif // DAWN_PLATFORM_IS(ANDROID) | |
| } | |
| // static | |
| ResultOrError<Ref<SharedTextureMemory>> SharedTextureMemory::Create( | |
| Device* device, | |
| StringView label, | |
| const SharedTextureMemoryOpaqueFDDescriptor* descriptor) { | |
| #if DAWN_PLATFORM_IS(POSIX) | |
| OpaqueFDImporter importer(device, descriptor); | |
| return importer.DoImport(label); | |
| #else | |
| DAWN_UNREACHABLE(); | |
| #endif // DAWN_PLATFORM_IS(POSIX) | |
| } | |
| SharedTextureMemory::SharedTextureMemory(Device* device, | |
| StringView label, | |
| const SharedTextureMemoryProperties& properties, | |
| uint32_t queueFamilyIndex, | |
| const YCbCrVkDescriptor& yCbCrVkDesc) | |
| : SharedTextureMemoryBase(device, label, properties), | |
| mQueueFamilyIndex(queueFamilyIndex), | |
| mYCbCrVkDesc(yCbCrVkDesc) {} | |
| RefCountedVkHandle<VkDeviceMemory>* SharedTextureMemory::GetVkDeviceMemory() const { | |
| return mVkDeviceMemory.Get(); | |
| } | |
| RefCountedVkHandle<VkImage>* SharedTextureMemory::GetVkImage() const { | |
| return mVkImage.Get(); | |
| } | |
| uint32_t SharedTextureMemory::GetQueueFamilyIndex() const { | |
| return mQueueFamilyIndex; | |
| } | |
| void SharedTextureMemory::DestroyImpl(DestroyReason reason) { | |
| mVkImage = nullptr; | |
| mVkDeviceMemory = nullptr; | |
| } | |
| Ref<SharedResourceMemoryContents> SharedTextureMemory::CreateContents() { | |
| return AcquireRef(new SharedTextureMemoryContentsVk(GetWeakRef(this), mYCbCrVkDesc)); | |
| } | |
| ResultOrError<Ref<TextureBase>> SharedTextureMemory::CreateTextureImpl( | |
| const UnpackedPtr<TextureDescriptor>& descriptor) { | |
| return SharedTexture::Create(this, descriptor); | |
| } | |
| MaybeValError SharedTextureMemory::BeginAccessImpl( | |
| TextureBase* texture, | |
| const UnpackedPtr<BeginAccessDescriptor>& descriptor) { | |
| // TODO(dawn/2276): support concurrent read access. | |
| DAWN_INVALID_IF(descriptor->concurrentRead, "Vulkan backend doesn't support concurrent read."); | |
| DAWN_INVALID_IF(texture->GetFormat().format == wgpu::TextureFormat::OpaqueYCbCrAndroid && | |
| !descriptor->initialized, | |
| "BeginAccess with Texture format (%s) must be initialized", | |
| texture->GetFormat().format); | |
| wgpu::SType type; | |
| DAWN_TRY_ASSIGN( | |
| type, (descriptor.ValidateBranches<Branch<SharedTextureMemoryVkImageLayoutBeginState>>())); | |
| DAWN_ASSERT(type == wgpu::SType::SharedTextureMemoryVkImageLayoutBeginState); | |
| auto vkLayoutBeginState = descriptor.Get<SharedTextureMemoryVkImageLayoutBeginState>(); | |
| DAWN_ASSERT(vkLayoutBeginState != nullptr); | |
| for (auto [i, fence] : Enumerate(descriptor->fences)) { | |
| // All fences are backed by binary semaphores. | |
| DAWN_INVALID_IF(descriptor->signaledValues[i] != 1, "%s signaled value (%u) was not 1.", | |
| fence, descriptor->signaledValues[i]); | |
| } | |
| static_cast<SharedTexture*>(texture)->SetPendingAcquire( | |
| static_cast<VkImageLayout>(vkLayoutBeginState->oldLayout), | |
| static_cast<VkImageLayout>(vkLayoutBeginState->newLayout)); | |
| // TODO(crbug.com/449708316): Better identify textures used as a swapchain. | |
| ToBackend(texture)->SetIsExternalSwapchainTexture(true); | |
| return {}; | |
| } | |
| #if DAWN_PLATFORM_IS(FUCHSIA) || DAWN_PLATFORM_IS(LINUX) | |
| ResultOrError<FenceAndSignalValue> SharedTextureMemory::EndAccessImpl( | |
| TextureBase* texture, | |
| ExecutionSerial lastUsageSerial, | |
| UnpackedPtr<EndAccessState>& state) { | |
| wgpu::SType type; | |
| DAWN_TRY_ASSIGN(type, | |
| (state.ValidateBranches<Branch<SharedTextureMemoryVkImageLayoutEndState>>())); | |
| DAWN_ASSERT(type == wgpu::SType::SharedTextureMemoryVkImageLayoutEndState); | |
| auto vkLayoutEndState = state.Get<SharedTextureMemoryVkImageLayoutEndState>(); | |
| DAWN_ASSERT(vkLayoutEndState != nullptr); | |
| #if DAWN_PLATFORM_IS(FUCHSIA) | |
| DAWN_INVALID_IF(!GetDevice()->HasFeature(Feature::SharedFenceVkSemaphoreZirconHandle), | |
| "Required feature (%s) for %s is missing.", | |
| wgpu::FeatureName::SharedFenceVkSemaphoreZirconHandle, | |
| wgpu::SharedFenceType::VkSemaphoreZirconHandle); | |
| #elif DAWN_PLATFORM_IS(LINUX) | |
| DAWN_INVALID_IF(!GetDevice()->HasFeature(Feature::SharedFenceSyncFD) && | |
| !GetDevice()->HasFeature(Feature::SharedFenceVkSemaphoreOpaqueFD), | |
| "Required feature (%s or %s) for %s or %s is missing.", | |
| wgpu::FeatureName::SharedFenceVkSemaphoreOpaqueFD, | |
| wgpu::FeatureName::SharedFenceSyncFD, | |
| wgpu::SharedFenceType::VkSemaphoreOpaqueFD, wgpu::SharedFenceType::SyncFD); | |
| #endif | |
| SystemHandle handle; | |
| { | |
| ExternalSemaphoreHandle semaphoreHandle; | |
| VkImageLayout releasedOldLayout; | |
| VkImageLayout releasedNewLayout; | |
| DAWN_TRY(static_cast<SharedTexture*>(texture)->EndAccess( | |
| &semaphoreHandle, &releasedOldLayout, &releasedNewLayout)); | |
| // Handle is acquired from the texture so we need to make sure to close it. | |
| // TODO(dawn:1745): Consider using one event per submit that is tracked by the | |
| // CommandRecordingContext so that we don't need to create one handle per texture, | |
| // and so we don't need to acquire it here to close it. | |
| handle = SystemHandle::Acquire(semaphoreHandle); | |
| vkLayoutEndState->oldLayout = releasedOldLayout; | |
| vkLayoutEndState->newLayout = releasedNewLayout; | |
| } | |
| Ref<SharedFence> fence; | |
| #if DAWN_PLATFORM_IS(FUCHSIA) | |
| SharedFenceVkSemaphoreZirconHandleDescriptor desc; | |
| desc.handle = handle.Get(); | |
| DAWN_TRY_ASSIGN(fence, | |
| SharedFence::Create(ToBackend(GetDevice()), "Internal VkSemaphore", &desc)); | |
| #elif DAWN_PLATFORM_IS(LINUX) | |
| if (GetDevice()->HasFeature(Feature::SharedFenceSyncFD)) { | |
| SharedFenceSyncFDDescriptor desc; | |
| desc.handle = handle.Get(); | |
| DAWN_TRY_ASSIGN(fence, | |
| SharedFence::Create(ToBackend(GetDevice()), "Internal VkSemaphore", &desc)); | |
| } else { | |
| SharedFenceVkSemaphoreOpaqueFDDescriptor desc; | |
| desc.handle = handle.Get(); | |
| DAWN_TRY_ASSIGN(fence, | |
| SharedFence::Create(ToBackend(GetDevice()), "Internal VkSemaphore", &desc)); | |
| } | |
| #endif | |
| ToBackend(texture)->NotifySwapChainPresent(); | |
| // All semaphores are binary semaphores. | |
| return FenceAndSignalValue{std::move(fence), 1}; | |
| } | |
| #else // DAWN_PLATFORM_IS(FUCHSIA) || DAWN_PLATFORM_IS(LINUX) | |
| ResultOrError<FenceAndSignalValue> SharedTextureMemory::EndAccessImpl( | |
| TextureBase* texture, | |
| ExecutionSerial lastUsageSerial, | |
| UnpackedPtr<EndAccessState>& state) { | |
| return DAWN_VALIDATION_ERROR("No shared fence features supported."); | |
| } | |
| #endif // DAWN_PLATFORM_IS(FUCHSIA) || DAWN_PLATFORM_IS(LINUX) | |
| MaybeValError SharedTextureMemory::GetChainedProperties( | |
| UnpackedPtr<SharedTextureMemoryProperties>& properties) const { | |
| auto ahbProperties = properties.Get<SharedTextureMemoryAHardwareBufferProperties>(); | |
| if (!ahbProperties) { | |
| return {}; | |
| } | |
| DAWN_INVALID_IF(ahbProperties->yCbCrInfo.nextInChain, | |
| "yCBCrInfo field of SharedTextureMemoryAHardwareBufferProperties has a chained " | |
| "struct."); | |
| ahbProperties->yCbCrInfo = mYCbCrVkDesc; | |
| return {}; | |
| } | |
| // SharedTextureMemoryContentsVk | |
| SharedTextureMemoryContentsVk::SharedTextureMemoryContentsVk( | |
| WeakRef<SharedTextureMemoryBase> sharedTextureMemory, | |
| YCbCrVkDescriptor ycbcrVkDesc) | |
| : SharedTextureMemoryContents(std::move(sharedTextureMemory)), mYCbCrVkDesc(ycbcrVkDesc) {} | |
| bool SharedTextureMemoryContentsVk::IsYCbCrFilterable() const { | |
| return mYCbCrVkDesc.vkChromaFilter != wgpu::FilterMode::Nearest; | |
| } | |
| const YCbCrVkDescriptor& SharedTextureMemoryContentsVk::GetYCbCrVkDesc() const { | |
| return mYCbCrVkDesc; | |
| } | |
| } // namespace dawn::native::vulkan |