vulkan: Use separate release semaphores for external textures
Chromium's ExternalVkImageBacking does not expect multiple different
external textures to be sharing the same release semaphore. This causes
freezes with
https://webgpu.github.io/webgpu-samples/samples/videoUploading on Linux
and other cases where multiple shared images are involved. Fix this by
creating a separate semaphore for each external texture being released.
Bug: chromium:1411745
Change-Id: Ic5ebe9e1e97340e7baedea5ea23c179aa26ea4e7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/141420
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Ben Clayton <bclayton@chromium.org>
diff --git a/AUTHORS b/AUTHORS
index bded374..efca49e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -5,3 +5,4 @@
# of contributors, see the revision history in source control.
Google LLC
Vasyl Teliman
+Phan Quang Minh
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index fbe8172..399e114 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -50,10 +50,12 @@
namespace {
// Destroy the semaphore when out of scope.
-class ScopedSignalSemaphore : public NonMovable {
+class ScopedSignalSemaphore : public NonCopyable {
public:
ScopedSignalSemaphore(Device* device, VkSemaphore semaphore)
: mDevice(device), mSemaphore(semaphore) {}
+ ScopedSignalSemaphore(ScopedSignalSemaphore&& other)
+ : mDevice(other.mDevice), mSemaphore(std::exchange(other.mSemaphore, VK_NULL_HANDLE)) {}
~ScopedSignalSemaphore() {
if (mSemaphore != VK_NULL_HANDLE) {
mDevice->GetFencedDeleter()->DeleteWhenUnused(mSemaphore);
@@ -314,10 +316,12 @@
fn, &mRecordingContext, mRecordingContext.mappableBuffersForEagerTransition);
}
- ScopedSignalSemaphore externalTextureSemaphore(this, VK_NULL_HANDLE);
- if (mRecordingContext.externalTexturesForEagerTransition.size() > 0) {
- // Create an external semaphore for all external textures that have been used in the pending
- // submit.
+ std::vector<ScopedSignalSemaphore> externalTextureSemaphores;
+ for (size_t i = 0; i < mRecordingContext.externalTexturesForEagerTransition.size(); ++i) {
+ // Create an external semaphore for each external textures that have been used in the
+ // pending submit.
+ auto& externalTextureSemaphore =
+ externalTextureSemaphores.emplace_back(this, VK_NULL_HANDLE);
DAWN_TRY_ASSIGN(*externalTextureSemaphore.InitializeInto(),
mExternalSemaphoreService->CreateExportableSemaphore());
}
@@ -336,7 +340,7 @@
std::vector<VkPipelineStageFlags> dstStageMasks(mRecordingContext.waitSemaphores.size(),
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
- if (externalTextureSemaphore.Get() != VK_NULL_HANDLE) {
+ for (auto& externalTextureSemaphore : externalTextureSemaphores) {
mRecordingContext.signalSemaphores.push_back(externalTextureSemaphore.Get());
}
@@ -376,23 +380,19 @@
mCommandsInFlight.Enqueue(submittedCommands, lastSubmittedSerial);
}
- if (mRecordingContext.externalTexturesForEagerTransition.size() > 0) {
+ auto externalTextureSemaphoreIter = externalTextureSemaphores.begin();
+ for (auto* texture : mRecordingContext.externalTexturesForEagerTransition) {
// Export the signal semaphore.
ExternalSemaphoreHandle semaphoreHandle;
- DAWN_TRY_ASSIGN(semaphoreHandle,
- mExternalSemaphoreService->ExportSemaphore(externalTextureSemaphore.Get()));
+ DAWN_TRY_ASSIGN(semaphoreHandle, mExternalSemaphoreService->ExportSemaphore(
+ externalTextureSemaphoreIter->Get()));
+ ++externalTextureSemaphoreIter;
// Update all external textures, eagerly transitioned in the submit, with the exported
- // handle, and the duplicated handles.
- bool first = true;
- for (auto* texture : mRecordingContext.externalTexturesForEagerTransition) {
- ExternalSemaphoreHandle handle =
- (first ? semaphoreHandle
- : mExternalSemaphoreService->DuplicateHandle(semaphoreHandle));
- first = false;
- texture->UpdateExternalSemaphoreHandle(handle);
- }
+ // handles.
+ texture->UpdateExternalSemaphoreHandle(semaphoreHandle);
}
+ DAWN_ASSERT(externalTextureSemaphoreIter == externalTextureSemaphores.end());
mRecordingContext = CommandRecordingContext();
DAWN_TRY(PrepareRecordingContext());
diff --git a/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp b/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp
index f79568e..90b3e19 100644
--- a/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp
+++ b/src/dawn/tests/white_box/VulkanImageWrappingTests.cpp
@@ -40,6 +40,9 @@
using UseDedicatedAllocation = bool;
using DetectDedicatedAllocation = bool;
+
+constexpr int kTestTexturesCount = 2;
+
DAWN_TEST_PARAM_STRUCT(ImageWrappingParams, UseDedicatedAllocation, DetectDedicatedAllocation);
class VulkanImageWrappingTestBase : public DawnTestWithParams<ImageWrappingParams> {
@@ -74,8 +77,11 @@
defaultDescriptor.usage = wgpu::TextureUsage::RenderAttachment |
wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
- defaultTexture =
- mBackend->CreateTexture(1, 1, defaultDescriptor.format, defaultDescriptor.usage);
+ for (int i = 0; i < kTestTexturesCount; ++i) {
+ testTextures[i] =
+ mBackend->CreateTexture(1, 1, defaultDescriptor.format, defaultDescriptor.usage);
+ }
+ defaultTexture = testTextures[0].get();
}
void TearDown() override {
@@ -84,6 +90,7 @@
return;
}
+ testTextures = {};
defaultTexture = nullptr;
mBackend = nullptr;
DawnTestWithParams::TearDown();
@@ -142,7 +149,8 @@
std::unique_ptr<VulkanImageWrappingTestBackend> mBackend;
wgpu::TextureDescriptor defaultDescriptor;
- std::unique_ptr<ExternalTexture> defaultTexture;
+ std::array<std::unique_ptr<ExternalTexture>, kTestTexturesCount> testTextures;
+ ExternalTexture* defaultTexture;
};
using VulkanImageWrappingValidationTests = VulkanImageWrappingTestBase;
@@ -150,7 +158,7 @@
// Test no error occurs if the import is valid
TEST_P(VulkanImageWrappingValidationTests, SuccessfulImport) {
wgpu::Texture texture =
- WrapVulkanImage(device, &defaultDescriptor, defaultTexture.get(), {}, true, true);
+ WrapVulkanImage(device, &defaultDescriptor, defaultTexture, {}, true, true);
EXPECT_NE(texture.Get(), nullptr);
IgnoreSignalSemaphore(texture);
}
@@ -163,7 +171,7 @@
internalDesc.sType = wgpu::SType::DawnTextureInternalUsageDescriptor;
wgpu::Texture texture =
- WrapVulkanImage(device, &defaultDescriptor, defaultTexture.get(), {}, true, true);
+ WrapVulkanImage(device, &defaultDescriptor, defaultTexture, {}, true, true);
EXPECT_NE(texture.Get(), nullptr);
IgnoreSignalSemaphore(texture);
}
@@ -174,8 +182,8 @@
chainedDescriptor.sType = wgpu::SType::SurfaceDescriptorFromWindowsSwapChainPanel;
defaultDescriptor.nextInChain = &chainedDescriptor;
- ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
- device, &defaultDescriptor, defaultTexture.get(), {}, true, false));
+ ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(device, &defaultDescriptor,
+ defaultTexture, {}, true, false));
EXPECT_EQ(texture.Get(), nullptr);
}
@@ -183,8 +191,8 @@
TEST_P(VulkanImageWrappingValidationTests, InvalidTextureDimension) {
defaultDescriptor.dimension = wgpu::TextureDimension::e1D;
- ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
- device, &defaultDescriptor, defaultTexture.get(), {}, true, false));
+ ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(device, &defaultDescriptor,
+ defaultTexture, {}, true, false));
EXPECT_EQ(texture.Get(), nullptr);
}
@@ -192,8 +200,8 @@
TEST_P(VulkanImageWrappingValidationTests, InvalidMipLevelCount) {
defaultDescriptor.mipLevelCount = 2;
- ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
- device, &defaultDescriptor, defaultTexture.get(), {}, true, false));
+ ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(device, &defaultDescriptor,
+ defaultTexture, {}, true, false));
EXPECT_EQ(texture.Get(), nullptr);
}
@@ -201,8 +209,8 @@
TEST_P(VulkanImageWrappingValidationTests, InvalidDepth) {
defaultDescriptor.size.depthOrArrayLayers = 2;
- ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
- device, &defaultDescriptor, defaultTexture.get(), {}, true, false));
+ ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(device, &defaultDescriptor,
+ defaultTexture, {}, true, false));
EXPECT_EQ(texture.Get(), nullptr);
}
@@ -210,15 +218,15 @@
TEST_P(VulkanImageWrappingValidationTests, InvalidSampleCount) {
defaultDescriptor.sampleCount = 4;
- ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(
- device, &defaultDescriptor, defaultTexture.get(), {}, true, false));
+ ASSERT_DEVICE_ERROR(wgpu::Texture texture = WrapVulkanImage(device, &defaultDescriptor,
+ defaultTexture, {}, true, false));
EXPECT_EQ(texture.Get(), nullptr);
}
// Test an error occurs if we try to export the signal semaphore twice
TEST_P(VulkanImageWrappingValidationTests, DoubleSignalSemaphoreExport) {
wgpu::Texture texture =
- WrapVulkanImage(device, &defaultDescriptor, defaultTexture.get(), {}, true, true);
+ WrapVulkanImage(device, &defaultDescriptor, defaultTexture, {}, true, true);
ASSERT_NE(texture.Get(), nullptr);
IgnoreSignalSemaphore(texture);
@@ -242,7 +250,7 @@
// Test an error occurs if we try to export the signal semaphore from a destroyed texture
TEST_P(VulkanImageWrappingValidationTests, DestroyedTextureSignalSemaphoreExport) {
wgpu::Texture texture =
- WrapVulkanImage(device, &defaultDescriptor, defaultTexture.get(), {}, true, true);
+ WrapVulkanImage(device, &defaultDescriptor, defaultTexture, {}, true, true);
ASSERT_NE(texture.Get(), nullptr);
texture.Destroy();
@@ -298,6 +306,31 @@
wgpu::CommandBuffer commands = encoder.Finish();
wgpu::Queue queue = dawnDevice.GetQueue();
+
+ queue.Submit(1, &commands);
+ }
+
+ void ClearImages(wgpu::Device dawnDevice,
+ std::vector<wgpu::Texture> wrappedTextures,
+ wgpu::Color clearColor) {
+ wgpu::CommandEncoder encoder = dawnDevice.CreateCommandEncoder();
+
+ for (auto wrappedTexture : wrappedTextures) {
+ wgpu::TextureView wrappedView = wrappedTexture.CreateView();
+
+ // Submit a clear operation
+ utils::ComboRenderPassDescriptor renderPassDescriptor({wrappedView}, {});
+ renderPassDescriptor.cColorAttachments[0].clearValue = clearColor;
+ renderPassDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
+
+ wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescriptor);
+ pass.End();
+ }
+
+ wgpu::CommandBuffer commands = encoder.Finish();
+
+ wgpu::Queue queue = dawnDevice.GetQueue();
+
queue.Submit(1, &commands);
}
@@ -324,7 +357,7 @@
TEST_P(VulkanImageWrappingUsageTests, ClearImageAcrossDevices) {
// Import the image on |secondDevice|
wgpu::Texture wrappedTexture =
- WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture.get(), {},
+ WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture, {},
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
// Clear |wrappedTexture| on |secondDevice|
@@ -335,7 +368,7 @@
// Import the image to |device|, making sure we wait on signalFd
wgpu::Texture nextWrappedTexture = WrapVulkanImage(
- device, &defaultDescriptor, defaultTexture.get(), std::move(exportInfo.semaphores),
+ device, &defaultDescriptor, defaultTexture, std::move(exportInfo.semaphores),
exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
// Verify |device| sees the changes from |secondDevice|
@@ -344,12 +377,46 @@
IgnoreSignalSemaphore(nextWrappedTexture);
}
+// Clear two images in |secondDevice|
+// Verify clear color is visible in |device|
+// This is intended to verify that waiting on the signalFd for one external texture does not affect
+// those of other external textures.
+TEST_P(VulkanImageWrappingUsageTests, ClearTwoImagesAcrossDevices) {
+ static_assert(kTestTexturesCount >= 2);
+
+ std::vector<wgpu::Texture> wrappedTextures;
+ for (int i = 0; i < 2; ++i) {
+ // Import the images on |secondDevice|
+ wrappedTextures.push_back(
+ WrapVulkanImage(secondDevice, &defaultDescriptor, testTextures[i].get(), {},
+ VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL));
+ }
+
+ // Clear |wrappedTextures| on |secondDevice|
+ ClearImages(secondDevice, wrappedTextures, {1 / 255.0f, 2 / 255.0f, 3 / 255.0f, 4 / 255.0f});
+
+ for (int i = 0; i < 2; ++i) {
+ ExternalImageExportInfoVkForTesting exportInfo;
+ ASSERT_TRUE(mBackend->ExportImage(wrappedTextures[i], &exportInfo));
+
+ // Import the image to |device|, making sure we wait on signalFd
+ wgpu::Texture nextWrappedTexture = WrapVulkanImage(
+ device, &defaultDescriptor, testTextures[i].get(), std::move(exportInfo.semaphores),
+ exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
+
+ // Verify |device| sees the changes from |secondDevice|
+ EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(1, 2, 3, 4), nextWrappedTexture, 0, 0);
+
+ IgnoreSignalSemaphore(nextWrappedTexture);
+ }
+}
+
// Clear an image in |secondDevice|
// Verify clear color is not visible in |device| if we import the texture as not cleared
TEST_P(VulkanImageWrappingUsageTests, UninitializedTextureIsCleared) {
// Import the image on |secondDevice|
wgpu::Texture wrappedTexture =
- WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture.get(), {},
+ WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture, {},
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
// Clear |wrappedTexture| on |secondDevice|
@@ -360,7 +427,7 @@
// Import the image to |device|, making sure we wait on signalFd
wgpu::Texture nextWrappedTexture = WrapVulkanImage(
- device, &defaultDescriptor, defaultTexture.get(), std::move(exportInfo.semaphores),
+ device, &defaultDescriptor, defaultTexture, std::move(exportInfo.semaphores),
exportInfo.releasedOldLayout, exportInfo.releasedNewLayout, false);
// Verify |device| doesn't see the changes from |secondDevice|
@@ -376,7 +443,7 @@
TEST_P(VulkanImageWrappingUsageTests, CopyTextureToTextureSrcSync) {
// Import the image on |secondDevice|
wgpu::Texture wrappedTexture =
- WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture.get(), {},
+ WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture, {},
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
// Clear |wrappedTexture| on |secondDevice|
@@ -387,7 +454,7 @@
// Import the image to |device|, making sure we wait on |signalFd|
wgpu::Texture deviceWrappedTexture = WrapVulkanImage(
- device, &defaultDescriptor, defaultTexture.get(), std::move(exportInfo.semaphores),
+ device, &defaultDescriptor, defaultTexture, std::move(exportInfo.semaphores),
exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
// Create a second texture on |device|
@@ -414,8 +481,8 @@
TEST_P(VulkanImageWrappingUsageTests, CopyTextureToTextureDstSync) {
// Import the image on |device|
wgpu::Texture wrappedTexture =
- WrapVulkanImage(device, &defaultDescriptor, defaultTexture.get(), {},
- VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+ WrapVulkanImage(device, &defaultDescriptor, defaultTexture, {}, VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
// Clear |wrappedTexture| on |device|
ClearImage(device, wrappedTexture, {5 / 255.0f, 6 / 255.0f, 7 / 255.0f, 8 / 255.0f});
@@ -425,7 +492,7 @@
// Import the image to |secondDevice|, making sure we wait on |signalFd|
wgpu::Texture secondDeviceWrappedTexture = WrapVulkanImage(
- secondDevice, &defaultDescriptor, defaultTexture.get(), std::move(exportInfo.semaphores),
+ secondDevice, &defaultDescriptor, defaultTexture, std::move(exportInfo.semaphores),
exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
// Create a texture with color B on |secondDevice|
@@ -442,7 +509,7 @@
ASSERT_TRUE(mBackend->ExportImage(secondDeviceWrappedTexture, &secondExportInfo));
wgpu::Texture nextWrappedTexture = WrapVulkanImage(
- device, &defaultDescriptor, defaultTexture.get(), std::move(secondExportInfo.semaphores),
+ device, &defaultDescriptor, defaultTexture, std::move(secondExportInfo.semaphores),
secondExportInfo.releasedOldLayout, secondExportInfo.releasedNewLayout);
// Verify |nextWrappedTexture| contains the color from our copy
@@ -458,7 +525,7 @@
TEST_P(VulkanImageWrappingUsageTests, CopyTextureToBufferSrcSync) {
// Import the image on |secondDevice|
wgpu::Texture wrappedTexture =
- WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture.get(), {},
+ WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture, {},
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
// Clear |wrappedTexture| on |secondDevice|
@@ -469,7 +536,7 @@
// Import the image to |device|, making sure we wait on |signalFd|
wgpu::Texture deviceWrappedTexture = WrapVulkanImage(
- device, &defaultDescriptor, defaultTexture.get(), std::move(exportInfo.semaphores),
+ device, &defaultDescriptor, defaultTexture, std::move(exportInfo.semaphores),
exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
// Create a destination buffer on |device|
@@ -508,8 +575,8 @@
TEST_P(VulkanImageWrappingUsageTests, CopyBufferToTextureDstSync) {
// Import the image on |device|
wgpu::Texture wrappedTexture =
- WrapVulkanImage(device, &defaultDescriptor, defaultTexture.get(), {},
- VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+ WrapVulkanImage(device, &defaultDescriptor, defaultTexture, {}, VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
// Clear |wrappedTexture| on |device|
ClearImage(device, wrappedTexture, {5 / 255.0f, 6 / 255.0f, 7 / 255.0f, 8 / 255.0f});
@@ -519,7 +586,7 @@
// Import the image to |secondDevice|, making sure we wait on |signalFd|
wgpu::Texture secondDeviceWrappedTexture = WrapVulkanImage(
- secondDevice, &defaultDescriptor, defaultTexture.get(), std::move(exportInfo.semaphores),
+ secondDevice, &defaultDescriptor, defaultTexture, std::move(exportInfo.semaphores),
exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
// Copy color B on |secondDevice|
@@ -546,7 +613,7 @@
ASSERT_TRUE(mBackend->ExportImage(secondDeviceWrappedTexture, &secondExportInfo));
wgpu::Texture nextWrappedTexture = WrapVulkanImage(
- device, &defaultDescriptor, defaultTexture.get(), std::move(secondExportInfo.semaphores),
+ device, &defaultDescriptor, defaultTexture, std::move(secondExportInfo.semaphores),
secondExportInfo.releasedOldLayout, secondExportInfo.releasedNewLayout);
// Verify |nextWrappedTexture| contains the color from our copy
@@ -563,7 +630,7 @@
TEST_P(VulkanImageWrappingUsageTests, DoubleTextureUsage) {
// Import the image on |secondDevice|
wgpu::Texture wrappedTexture =
- WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture.get(), {},
+ WrapVulkanImage(secondDevice, &defaultDescriptor, defaultTexture, {},
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
// Clear |wrappedTexture| on |secondDevice|
@@ -574,7 +641,7 @@
// Import the image to |device|, making sure we wait on |signalFd|
wgpu::Texture deviceWrappedTexture = WrapVulkanImage(
- device, &defaultDescriptor, defaultTexture.get(), std::move(exportInfo.semaphores),
+ device, &defaultDescriptor, defaultTexture, std::move(exportInfo.semaphores),
exportInfo.releasedOldLayout, exportInfo.releasedNewLayout);
// Create a second texture on |device|