Add AHardwareBuffer support
Initial pass at adding AHardwareBufferSupport so that Dawn can display content on Android. Confirmed that this will allow many WebGPU pages to render when paired with https://chromium-review.googlesource.com/c/chromium/src/+/3877262 Bug: dawn:286 Change-Id: I627fa2ab71f85bd3cb7ea21b0588dbd2089cdf5f Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/101460 Commit-Queue: Brandon Jones <bajones@chromium.org> Reviewed-by: Corentin Wallez <cwallez@chromium.org> Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
parent
0d7cd27fd3
commit
7edef20bdd
|
@ -229,6 +229,7 @@ enum ExternalImageType {
|
|||
IOSurface,
|
||||
DXGISharedHandle,
|
||||
EGLImage,
|
||||
AHardwareBuffer,
|
||||
};
|
||||
|
||||
// Common properties of external images
|
||||
|
|
|
@ -126,6 +126,26 @@ struct DAWN_NATIVE_EXPORT ExternalImageExportInfoDmaBuf : ExternalImageExportInf
|
|||
ExternalImageExportInfoDmaBuf();
|
||||
};
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
||||
// Descriptor for AHardwareBuffer image import
|
||||
struct DAWN_NATIVE_EXPORT ExternalImageDescriptorAHardwareBuffer : ExternalImageDescriptorVk {
|
||||
public:
|
||||
ExternalImageDescriptorAHardwareBuffer();
|
||||
|
||||
struct AHardwareBuffer* handle; // The AHardwareBuffer which contains the memory of the image
|
||||
std::vector<int> waitFDs; // File descriptors of semaphores which will be waited on
|
||||
|
||||
protected:
|
||||
using ExternalImageDescriptorVk::ExternalImageDescriptorVk;
|
||||
};
|
||||
|
||||
struct DAWN_NATIVE_EXPORT ExternalImageExportInfoAHardwareBuffer : ExternalImageExportInfoFD {
|
||||
ExternalImageExportInfoAHardwareBuffer();
|
||||
};
|
||||
|
||||
#endif // __ANDROID__
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
// Imports external memory into a Vulkan image. Internally, this uses external memory /
|
||||
|
|
|
@ -690,6 +690,12 @@ source_set("sources") {
|
|||
"vulkan/external_memory/MemoryServiceZirconHandle.cpp",
|
||||
"vulkan/external_semaphore/SemaphoreServiceZirconHandle.cpp",
|
||||
]
|
||||
} else if (is_android) {
|
||||
sources += [
|
||||
"vulkan/external_memory/MemoryServiceAHardwareBuffer.cpp",
|
||||
"vulkan/external_semaphore/SemaphoreServiceFD.cpp",
|
||||
]
|
||||
defines += [ "DAWN_USE_SYNC_FDS" ]
|
||||
} else {
|
||||
sources += [
|
||||
"vulkan/external_memory/MemoryServiceNull.cpp",
|
||||
|
|
|
@ -19,7 +19,13 @@
|
|||
|
||||
namespace dawn::native::vulkan {
|
||||
|
||||
#if DAWN_PLATFORM_IS(LINUX)
|
||||
#if DAWN_PLATFORM_IS(ANDROID)
|
||||
// AHardwareBuffer
|
||||
using ExternalMemoryHandle = struct AHardwareBuffer*;
|
||||
// File descriptor
|
||||
using ExternalSemaphoreHandle = int;
|
||||
const ExternalSemaphoreHandle kNullExternalSemaphoreHandle = -1;
|
||||
#elif DAWN_PLATFORM_IS(LINUX)
|
||||
// File descriptor
|
||||
using ExternalMemoryHandle = int;
|
||||
// File descriptor
|
||||
|
|
|
@ -732,6 +732,7 @@ MaybeError Texture::InitializeFromExternal(const ExternalImageDescriptorVk* desc
|
|||
}
|
||||
|
||||
mExternalState = ExternalState::PendingAcquire;
|
||||
mExportQueueFamilyIndex = externalMemoryService->GetQueueFamilyIndex();
|
||||
|
||||
mPendingAcquireOldLayout = descriptor->releasedOldLayout;
|
||||
mPendingAcquireNewLayout = descriptor->releasedNewLayout;
|
||||
|
@ -829,7 +830,7 @@ void Texture::TransitionEagerlyForExport(CommandRecordingContext* recordingConte
|
|||
|
||||
Device* device = ToBackend(GetDevice());
|
||||
barrier.srcQueueFamilyIndex = device->GetGraphicsQueueFamily();
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
|
||||
barrier.dstQueueFamilyIndex = mExportQueueFamilyIndex;
|
||||
|
||||
// We don't know when the importing queue will need the texture, so pass
|
||||
// VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT to ensure the barrier happens-before any usage in the
|
||||
|
@ -950,7 +951,7 @@ void Texture::TweakTransitionForExternalUsage(CommandRecordingContext* recording
|
|||
|
||||
VkImageMemoryBarrier* barrier = &(*barriers)[transitionBarrierStart];
|
||||
// Transfer texture from external queue to graphics queue
|
||||
barrier->srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
|
||||
barrier->srcQueueFamilyIndex = mExportQueueFamilyIndex;
|
||||
barrier->dstQueueFamilyIndex = ToBackend(GetDevice())->GetGraphicsQueueFamily();
|
||||
|
||||
// srcAccessMask means nothing when importing. Queue transfers require a barrier on
|
||||
|
|
|
@ -176,6 +176,7 @@ class Texture final : public TextureBase {
|
|||
};
|
||||
ExternalState mExternalState = ExternalState::InternalOnly;
|
||||
ExternalState mLastExternalState = ExternalState::InternalOnly;
|
||||
uint32_t mExportQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL_KHR;
|
||||
|
||||
VkImageLayout mPendingAcquireOldLayout;
|
||||
VkImageLayout mPendingAcquireNewLayout;
|
||||
|
|
|
@ -74,9 +74,26 @@ ExternalImageExportInfoDmaBuf::ExternalImageExportInfoDmaBuf()
|
|||
: ExternalImageExportInfoFD(ExternalImageType::DmaBuf) {}
|
||||
#endif // DAWN_PLATFORM_IS(LINUX)
|
||||
|
||||
#if DAWN_PLATFORM_IS(ANDROID)
|
||||
ExternalImageDescriptorAHardwareBuffer::ExternalImageDescriptorAHardwareBuffer()
|
||||
: ExternalImageDescriptorVk(ExternalImageType::AHardwareBuffer) {}
|
||||
|
||||
ExternalImageExportInfoAHardwareBuffer::ExternalImageExportInfoAHardwareBuffer()
|
||||
: ExternalImageExportInfoFD(ExternalImageType::AHardwareBuffer) {}
|
||||
#endif
|
||||
|
||||
WGPUTexture WrapVulkanImage(WGPUDevice device, const ExternalImageDescriptorVk* descriptor) {
|
||||
#if DAWN_PLATFORM_IS(LINUX)
|
||||
switch (descriptor->GetType()) {
|
||||
#if DAWN_PLATFORM_IS(ANDROID)
|
||||
case ExternalImageType::AHardwareBuffer: {
|
||||
Device* backendDevice = ToBackend(FromAPI(device));
|
||||
const ExternalImageDescriptorAHardwareBuffer* ahbDescriptor =
|
||||
static_cast<const ExternalImageDescriptorAHardwareBuffer*>(descriptor);
|
||||
|
||||
return ToAPI(backendDevice->CreateTextureWrappingVulkanImage(
|
||||
ahbDescriptor, ahbDescriptor->handle, ahbDescriptor->waitFDs));
|
||||
}
|
||||
#elif DAWN_PLATFORM_IS(LINUX)
|
||||
case ExternalImageType::OpaqueFD:
|
||||
case ExternalImageType::DmaBuf: {
|
||||
Device* backendDevice = ToBackend(FromAPI(device));
|
||||
|
@ -86,12 +103,11 @@ WGPUTexture WrapVulkanImage(WGPUDevice device, const ExternalImageDescriptorVk*
|
|||
return ToAPI(backendDevice->CreateTextureWrappingVulkanImage(
|
||||
fdDescriptor, fdDescriptor->memoryFD, fdDescriptor->waitFDs));
|
||||
}
|
||||
#endif // DAWN_PLATFORM_IS(LINUX)
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
return nullptr;
|
||||
#endif // DAWN_PLATFORM_IS(LINUX)
|
||||
}
|
||||
|
||||
bool ExportVulkanImage(WGPUTexture texture,
|
||||
|
@ -100,8 +116,9 @@ bool ExportVulkanImage(WGPUTexture texture,
|
|||
if (texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
#if DAWN_PLATFORM_IS(LINUX)
|
||||
#if DAWN_PLATFORM_IS(ANDROID) || DAWN_PLATFORM_IS(LINUX)
|
||||
switch (info->GetType()) {
|
||||
case ExternalImageType::AHardwareBuffer:
|
||||
case ExternalImageType::OpaqueFD:
|
||||
case ExternalImageType::DmaBuf: {
|
||||
Texture* backendTexture = ToBackend(FromAPI(texture));
|
||||
|
|
|
@ -386,6 +386,13 @@ MaybeError VulkanFunctions::LoadDeviceProcs(VkDevice device, const VulkanDeviceI
|
|||
}
|
||||
#endif
|
||||
|
||||
#if DAWN_PLATFORM_IS(ANDROID)
|
||||
if (deviceInfo.HasExt(DeviceExt::ExternalMemoryAndroidHardwareBuffer)) {
|
||||
GET_DEVICE_PROC(GetAndroidHardwareBufferPropertiesANDROID);
|
||||
GET_DEVICE_PROC(GetMemoryAndroidHardwareBufferANDROID);
|
||||
}
|
||||
#endif // DAWN_PLATFORM_IS(ANDROID)
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -165,7 +165,14 @@ struct VulkanFunctions {
|
|||
#endif // DAWN_PLATFORM_IS(WINDOWS)
|
||||
|
||||
#if DAWN_PLATFORM_IS(ANDROID)
|
||||
// KHR_android_surface
|
||||
VkFn<PFN_vkCreateAndroidSurfaceKHR> CreateAndroidSurfaceKHR = nullptr;
|
||||
|
||||
// VK_ANDROID_external_memory_android_hardware_buffer
|
||||
VkFn<PFN_vkGetAndroidHardwareBufferPropertiesANDROID>
|
||||
GetAndroidHardwareBufferPropertiesANDROID = nullptr;
|
||||
VkFn<PFN_vkGetMemoryAndroidHardwareBufferANDROID> GetMemoryAndroidHardwareBufferANDROID =
|
||||
nullptr;
|
||||
#endif // DAWN_PLATFORM_IS(ANDROID)
|
||||
|
||||
#if defined(DAWN_USE_X11)
|
||||
|
|
|
@ -57,6 +57,9 @@ class Service {
|
|||
const ExternalImageDescriptor* descriptor,
|
||||
VkImage image);
|
||||
|
||||
// Returns the index of the queue memory from this services should be exported with.
|
||||
uint32_t GetQueueFamilyIndex();
|
||||
|
||||
// Given an external handle pointing to memory, import it into a VkDeviceMemory
|
||||
ResultOrError<VkDeviceMemory> ImportMemory(ExternalMemoryHandle handle,
|
||||
const MemoryImportParams& importParams,
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
// Copyright 2022 The Dawn Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "dawn/common/Assert.h"
|
||||
#include "dawn/native/vulkan/AdapterVk.h"
|
||||
#include "dawn/native/vulkan/BackendVk.h"
|
||||
#include "dawn/native/vulkan/DeviceVk.h"
|
||||
#include "dawn/native/vulkan/TextureVk.h"
|
||||
#include "dawn/native/vulkan/UtilsVulkan.h"
|
||||
#include "dawn/native/vulkan/VulkanError.h"
|
||||
#include "dawn/native/vulkan/external_memory/MemoryService.h"
|
||||
|
||||
namespace dawn::native::vulkan::external_memory {
|
||||
|
||||
Service::Service(Device* device)
|
||||
: mDevice(device), mSupported(CheckSupport(device->GetDeviceInfo())) {}
|
||||
|
||||
Service::~Service() = default;
|
||||
|
||||
// static
|
||||
bool Service::CheckSupport(const VulkanDeviceInfo& deviceInfo) {
|
||||
return deviceInfo.HasExt(DeviceExt::ExternalMemoryAndroidHardwareBuffer);
|
||||
}
|
||||
|
||||
bool Service::SupportsImportMemory(VkFormat format,
|
||||
VkImageType type,
|
||||
VkImageTiling tiling,
|
||||
VkImageUsageFlags usage,
|
||||
VkImageCreateFlags flags) {
|
||||
// Early out before we try using extension functions
|
||||
if (!mSupported) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VkPhysicalDeviceImageFormatInfo2 formatInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2_KHR,
|
||||
.pNext = nullptr,
|
||||
.format = format,
|
||||
.type = type,
|
||||
.tiling = tiling,
|
||||
.usage = usage,
|
||||
.flags = flags,
|
||||
};
|
||||
|
||||
PNextChainBuilder formatInfoChain(&formatInfo);
|
||||
|
||||
VkPhysicalDeviceExternalImageFormatInfo externalFormatInfo = {
|
||||
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
|
||||
};
|
||||
formatInfoChain.Add(&externalFormatInfo,
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO_KHR);
|
||||
|
||||
VkImageFormatProperties2 formatProperties = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2_KHR,
|
||||
.pNext = nullptr,
|
||||
};
|
||||
|
||||
PNextChainBuilder formatPropertiesChain(&formatProperties);
|
||||
|
||||
VkExternalImageFormatProperties externalFormatProperties;
|
||||
formatPropertiesChain.Add(&externalFormatProperties,
|
||||
VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES_KHR);
|
||||
|
||||
VkResult result = VkResult::WrapUnsafe(mDevice->fn.GetPhysicalDeviceImageFormatProperties2(
|
||||
ToBackend(mDevice->GetAdapter())->GetPhysicalDevice(), &formatInfo, &formatProperties));
|
||||
|
||||
// If handle not supported, result == VK_ERROR_FORMAT_NOT_SUPPORTED
|
||||
if (result != VK_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(http://crbug.com/dawn/206): Investigate dedicated only images
|
||||
VkFlags memoryFlags = externalFormatProperties.externalMemoryProperties.externalMemoryFeatures;
|
||||
return (memoryFlags & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT_KHR) != 0;
|
||||
}
|
||||
|
||||
bool Service::SupportsCreateImage(const ExternalImageDescriptor* descriptor,
|
||||
VkFormat format,
|
||||
VkImageUsageFlags usage,
|
||||
bool* supportsDisjoint) {
|
||||
*supportsDisjoint = false;
|
||||
return mSupported;
|
||||
}
|
||||
|
||||
ResultOrError<MemoryImportParams> Service::GetMemoryImportParams(
|
||||
const ExternalImageDescriptor* descriptor,
|
||||
VkImage image) {
|
||||
DAWN_INVALID_IF(descriptor->GetType() != ExternalImageType::AHardwareBuffer,
|
||||
"ExternalImageDescriptor is not an AHardwareBuffer descriptor.");
|
||||
|
||||
const ExternalImageDescriptorAHardwareBuffer* aHardwareBufferDescriptor =
|
||||
static_cast<const ExternalImageDescriptorAHardwareBuffer*>(descriptor);
|
||||
|
||||
VkAndroidHardwareBufferPropertiesANDROID bufferProperties = {
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
|
||||
.pNext = nullptr,
|
||||
};
|
||||
|
||||
PNextChainBuilder bufferPropertiesChain(&bufferProperties);
|
||||
|
||||
VkAndroidHardwareBufferFormatPropertiesANDROID bufferFormatProperties;
|
||||
bufferPropertiesChain.Add(&bufferFormatProperties,
|
||||
VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID);
|
||||
|
||||
DAWN_TRY(CheckVkSuccess(
|
||||
mDevice->fn.GetAndroidHardwareBufferPropertiesANDROID(
|
||||
mDevice->GetVkDevice(), aHardwareBufferDescriptor->handle, &bufferProperties),
|
||||
"vkGetAndroidHardwareBufferPropertiesANDROID"));
|
||||
|
||||
MemoryImportParams params = {bufferProperties.allocationSize, bufferProperties.memoryTypeBits};
|
||||
return params;
|
||||
}
|
||||
|
||||
uint32_t Service::GetQueueFamilyIndex() {
|
||||
return VK_QUEUE_FAMILY_FOREIGN_EXT;
|
||||
}
|
||||
|
||||
ResultOrError<VkDeviceMemory> Service::ImportMemory(ExternalMemoryHandle handle,
|
||||
const MemoryImportParams& importParams,
|
||||
VkImage image) {
|
||||
DAWN_INVALID_IF(handle == nullptr, "Importing memory with an invalid handle.");
|
||||
|
||||
VkMemoryRequirements requirements;
|
||||
mDevice->fn.GetImageMemoryRequirements(mDevice->GetVkDevice(), image, &requirements);
|
||||
DAWN_INVALID_IF(requirements.size > importParams.allocationSize,
|
||||
"Requested allocation size (%u) is smaller than the image requires (%u).",
|
||||
importParams.allocationSize, requirements.size);
|
||||
|
||||
VkMemoryAllocateInfo allocateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.allocationSize = importParams.allocationSize,
|
||||
.memoryTypeIndex = importParams.memoryTypeIndex,
|
||||
};
|
||||
|
||||
PNextChainBuilder allocateInfoChain(&allocateInfo);
|
||||
|
||||
VkImportAndroidHardwareBufferInfoANDROID importMemoryAHBInfo = {
|
||||
.buffer = handle,
|
||||
};
|
||||
allocateInfoChain.Add(&importMemoryAHBInfo,
|
||||
VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID);
|
||||
|
||||
VkDeviceMemory allocatedMemory = VK_NULL_HANDLE;
|
||||
DAWN_TRY(CheckVkSuccess(mDevice->fn.AllocateMemory(mDevice->GetVkDevice(), &allocateInfo,
|
||||
nullptr, &*allocatedMemory),
|
||||
"vkAllocateMemory"));
|
||||
return allocatedMemory;
|
||||
}
|
||||
|
||||
ResultOrError<VkImage> Service::CreateImage(const ExternalImageDescriptor* descriptor,
|
||||
const VkImageCreateInfo& baseCreateInfo) {
|
||||
VkImageCreateInfo createInfo = baseCreateInfo;
|
||||
createInfo.flags |= VK_IMAGE_CREATE_ALIAS_BIT_KHR;
|
||||
createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
PNextChainBuilder createInfoChain(&createInfo);
|
||||
|
||||
VkExternalMemoryImageCreateInfo externalMemoryImageCreateInfo = {
|
||||
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
|
||||
};
|
||||
createInfoChain.Add(&externalMemoryImageCreateInfo,
|
||||
VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO);
|
||||
|
||||
ASSERT(IsSampleCountSupported(mDevice, createInfo));
|
||||
|
||||
VkImage image;
|
||||
DAWN_TRY(CheckVkSuccess(
|
||||
mDevice->fn.CreateImage(mDevice->GetVkDevice(), &createInfo, nullptr, &*image),
|
||||
"CreateImage"));
|
||||
return image;
|
||||
}
|
||||
|
||||
} // namespace dawn::native::vulkan::external_memory
|
|
@ -256,6 +256,10 @@ ResultOrError<MemoryImportParams> Service::GetMemoryImportParams(
|
|||
return params;
|
||||
}
|
||||
|
||||
uint32_t Service::GetQueueFamilyIndex() {
|
||||
return VK_QUEUE_FAMILY_EXTERNAL_KHR;
|
||||
}
|
||||
|
||||
ResultOrError<VkDeviceMemory> Service::ImportMemory(ExternalMemoryHandle handle,
|
||||
const MemoryImportParams& importParams,
|
||||
VkImage image) {
|
||||
|
|
|
@ -51,6 +51,10 @@ ResultOrError<MemoryImportParams> Service::GetMemoryImportParams(
|
|||
return DAWN_UNIMPLEMENTED_ERROR("Using null memory service to interop inside Vulkan");
|
||||
}
|
||||
|
||||
uint32_t Service::GetQueueFamilyIndex() {
|
||||
return VK_QUEUE_FAMILY_EXTERNAL_KHR;
|
||||
}
|
||||
|
||||
ResultOrError<VkDeviceMemory> Service::ImportMemory(ExternalMemoryHandle handle,
|
||||
const MemoryImportParams& importParams,
|
||||
VkImage image) {
|
||||
|
|
|
@ -100,6 +100,10 @@ ResultOrError<MemoryImportParams> Service::GetMemoryImportParams(
|
|||
return params;
|
||||
}
|
||||
|
||||
uint32_t Service::GetQueueFamilyIndex() {
|
||||
return VK_QUEUE_FAMILY_EXTERNAL_KHR;
|
||||
}
|
||||
|
||||
ResultOrError<VkDeviceMemory> Service::ImportMemory(ExternalMemoryHandle handle,
|
||||
const MemoryImportParams& importParams,
|
||||
VkImage image) {
|
||||
|
|
|
@ -100,6 +100,10 @@ ResultOrError<MemoryImportParams> Service::GetMemoryImportParams(
|
|||
return params;
|
||||
}
|
||||
|
||||
uint32_t Service::GetQueueFamilyIndex() {
|
||||
return VK_QUEUE_FAMILY_EXTERNAL_KHR;
|
||||
}
|
||||
|
||||
ResultOrError<VkDeviceMemory> Service::ImportMemory(ExternalMemoryHandle handle,
|
||||
const MemoryImportParams& importParams,
|
||||
VkImage image) {
|
||||
|
|
Loading…
Reference in New Issue