Add more multithread tests.

Bug: dawn:1662
Change-Id: I2b2c66c6f9a7b512ae9f8010a082e7306feaa6f3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/122060
Commit-Queue: Quyen Le <lehoangquyen@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
Le Hoang Quyen 2023-04-24 20:12:00 +00:00 committed by Dawn LUCI CQ
parent 653e99478e
commit 23f4396177
8 changed files with 1141 additions and 61 deletions

View File

@ -15,6 +15,7 @@
#include "dawn/tests/DawnTest.h"
#include <algorithm>
#include <atomic>
#include <fstream>
#include <iomanip>
#include <regex>
@ -1146,6 +1147,9 @@ std::ostringstream& DawnTestBase::AddBufferExpectation(const char* file,
deferred.size = size;
deferred.expectation.reset(expectation);
// This expectation might be called from multiple threads
dawn::Mutex::AutoLock lg(&mMutex);
mDeferredExpectations.push_back(std::move(deferred));
mDeferredExpectations.back().message = std::make_unique<std::ostringstream>();
return *(mDeferredExpectations.back().message.get());
@ -1200,6 +1204,9 @@ std::ostringstream& DawnTestBase::AddTextureExpectationImpl(const char* file,
deferred.bytesPerRow = bytesPerRow;
deferred.expectation.reset(expectation);
// This expectation might be called from multiple threads
dawn::Mutex::AutoLock lg(&mMutex);
mDeferredExpectations.push_back(std::move(deferred));
mDeferredExpectations.back().message = std::make_unique<std::ostringstream>();
return *(mDeferredExpectations.back().message.get());
@ -1504,11 +1511,16 @@ void DawnTestBase::FlushWire() {
}
void DawnTestBase::WaitForAllOperations() {
bool done = false;
// Callback might be invoked on another thread that calls the same WaitABit() method, not
// necessarily the current thread. So we need to use atomic here.
std::atomic<bool> done(false);
device.GetQueue().OnSubmittedWorkDone(
0u, [](WGPUQueueWorkDoneStatus, void* userdata) { *static_cast<bool*>(userdata) = true; },
0u,
[](WGPUQueueWorkDoneStatus, void* userdata) {
*static_cast<std::atomic<bool>*>(userdata) = true;
},
&done);
while (!done) {
while (!done.load()) {
WaitABit();
}
}
@ -1526,6 +1538,9 @@ DawnTestBase::ReadbackReservation DawnTestBase::ReserveReadback(wgpu::Device tar
utils::CreateBufferFromData(targetDevice, initialBufferData.data(), readbackSize,
wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst);
// This readback might be called from multiple threads
dawn::Mutex::AutoLock lg(&mMutex);
ReadbackReservation reservation;
reservation.device = targetDevice;
reservation.buffer = slot.buffer;
@ -1551,7 +1566,7 @@ void DawnTestBase::MapSlotsSynchronously() {
}
// Busy wait until all map operations are done.
while (mNumPendingMapOperations != 0) {
while (mNumPendingMapOperations.load(std::memory_order_acquire) != 0) {
WaitABit();
}
}
@ -1562,7 +1577,8 @@ void DawnTestBase::SlotMapCallback(WGPUBufferMapAsyncStatus status, void* userda
status == WGPUBufferMapAsyncStatus_DeviceLost);
std::unique_ptr<MapReadUserdata> userdata(static_cast<MapReadUserdata*>(userdata_));
DawnTestBase* test = userdata->test;
test->mNumPendingMapOperations--;
dawn::Mutex::AutoLock lg(&test->mMutex);
ReadbackSlot* slot = &test->mReadbackSlots[userdata->slot];
if (status == WGPUBufferMapAsyncStatus_Success) {
@ -1571,6 +1587,8 @@ void DawnTestBase::SlotMapCallback(WGPUBufferMapAsyncStatus status, void* userda
} else {
slot->mappedData = nullptr;
}
test->mNumPendingMapOperations.fetch_sub(1, std::memory_order_release);
}
void DawnTestBase::ResolveExpectations() {
@ -1629,6 +1647,8 @@ void DawnTestBase::ResolveDeferredExpectationsNow() {
FlushWire();
MapSlotsSynchronously();
dawn::Mutex::AutoLock lg(&mMutex);
ResolveExpectations();
mDeferredExpectations.clear();

View File

@ -15,6 +15,7 @@
#ifndef SRC_DAWN_TESTS_DAWNTEST_H_
#define SRC_DAWN_TESTS_DAWNTEST_H_
#include <atomic>
#include <memory>
#include <queue>
#include <string>
@ -23,6 +24,7 @@
#include <vector>
#include "dawn/common/Log.h"
#include "dawn/common/Mutex.h"
#include "dawn/common/Platform.h"
#include "dawn/common/Preprocessor.h"
#include "dawn/dawn_proc_table.h"
@ -623,7 +625,7 @@ class DawnTestBase {
// Maps all the buffers and fill ReadbackSlot::mappedData
void MapSlotsSynchronously();
static void SlotMapCallback(WGPUBufferMapAsyncStatus status, void* userdata);
size_t mNumPendingMapOperations = 0;
std::atomic<size_t> mNumPendingMapOperations = 0;
// Reserve space where the data for an expectation can be copied
struct ReadbackReservation {
@ -656,6 +658,8 @@ class DawnTestBase {
WGPUDevice mLastCreatedBackendDevice;
std::unique_ptr<dawn::platform::Platform> mTestPlatform;
dawn::Mutex mMutex;
};
#define DAWN_SKIP_TEST_IF_BASE(condition, type, reason) \

View File

@ -19,6 +19,7 @@
#include <wrl/client.h>
#include <memory>
#include <mutex>
#include <thread>
#include <utility>
#include <vector>
@ -395,14 +396,11 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
queue.Submit(1, &commands);
}
void WrapAndClearD3D11Texture(
const wgpu::TextureDescriptor& dawnDescriptor,
const D3D11_TEXTURE2D_DESC& d3dDescriptor,
const wgpu::Color& clearColor,
wgpu::Texture* dawnTextureOut,
void CreateSharedD3D11Texture(const D3D11_TEXTURE2D_DESC& d3dDescriptor,
ID3D11Texture2D** d3d11TextureOut,
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI>* externalImageOut,
bool isInitialized = true) const {
ID3D11Fence** d3d11FenceOut,
HANDLE* sharedHandleOut,
HANDLE* fenceSharedHandleOut) const {
ComPtr<ID3D11Texture2D> d3d11Texture;
HRESULT hr = mD3d11Device->CreateTexture2D(&d3dDescriptor, nullptr, &d3d11Texture);
ASSERT_EQ(hr, S_OK);
@ -417,20 +415,10 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
&sharedHandle);
ASSERT_EQ(hr, S_OK);
ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
HANDLE fenceSharedHandle = nullptr;
ComPtr<ID3D11Fence> d3d11Fence;
ComPtr<ID3D11DeviceContext4> d3d11DeviceContext4;
if (GetParam().mSyncMode == SyncMode::kKeyedMutex) {
hr = d3d11Texture.As(&dxgiKeyedMutex);
ASSERT_EQ(hr, S_OK);
hr = dxgiKeyedMutex->AcquireSync(kDXGIKeyedMutexAcquireReleaseKey, INFINITE);
ASSERT_EQ(hr, S_OK);
} else {
if (GetParam().mSyncMode == SyncMode::kFence) {
ComPtr<ID3D11Device5> d3d11Device5;
hr = mD3d11Device.As(&d3d11Device5);
ASSERT_EQ(hr, S_OK);
@ -442,6 +430,33 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
ASSERT_EQ(hr, S_OK);
}
*d3d11TextureOut = d3d11Texture.Detach();
*d3d11FenceOut = d3d11Fence.Detach();
*sharedHandleOut = sharedHandle;
*fenceSharedHandleOut = fenceSharedHandle;
}
void ClearD3D11Texture(const wgpu::Color& clearColor,
ID3D11Texture2D* d3d11TexturePtr,
ID3D11Fence* d3d11Fence,
uint64_t fenceSignalValue) const {
ComPtr<ID3D11Texture2D> d3d11Texture = d3d11TexturePtr;
ComPtr<IDXGIResource1> dxgiResource;
HRESULT hr = d3d11Texture.As(&dxgiResource);
ASSERT_EQ(hr, S_OK);
ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
ComPtr<ID3D11DeviceContext4> d3d11DeviceContext4;
if (GetParam().mSyncMode == SyncMode::kKeyedMutex) {
hr = d3d11Texture.As(&dxgiKeyedMutex);
ASSERT_EQ(hr, S_OK);
hr = dxgiKeyedMutex->AcquireSync(kDXGIKeyedMutexAcquireReleaseKey, INFINITE);
ASSERT_EQ(hr, S_OK);
}
ComPtr<ID3D11RenderTargetView> d3d11RTV;
hr = mD3d11Device->CreateRenderTargetView(d3d11Texture.Get(), nullptr, &d3d11RTV);
ASSERT_EQ(hr, S_OK);
@ -451,7 +466,6 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
static_cast<float>(clearColor.b), static_cast<float>(clearColor.a)};
mD3d11DeviceContext->ClearRenderTargetView(d3d11RTV.Get(), colorRGBA);
constexpr uint64_t kFenceSignalValue = 1;
if (dxgiKeyedMutex) {
hr = dxgiKeyedMutex->ReleaseSync(kDXGIKeyedMutexAcquireReleaseKey);
ASSERT_EQ(hr, S_OK);
@ -460,9 +474,18 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
ASSERT_EQ(hr, S_OK);
// The fence starts with 0 signaled, but that won't capture the render target view clear
// above, so signal explicitly with 1 and make the next Dawn access wait on 1.
d3d11DeviceContext4->Signal(d3d11Fence.Get(), kFenceSignalValue);
d3d11DeviceContext4->Signal(d3d11Fence, fenceSignalValue);
}
}
void WaitAndWrapD3D11Texture(
const wgpu::TextureDescriptor& dawnDescriptor,
HANDLE sharedHandle,
HANDLE fenceSharedHandle,
uint64_t fenceWaitValue,
wgpu::Texture* dawnTextureOut,
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI>* externalImageOut,
bool isInitialized) const {
dawn::native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc = {};
externalImageDesc.sharedHandle = sharedHandle;
externalImageDesc.cTextureDescriptor =
@ -476,12 +499,36 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
externalAccessDesc.isInitialized = isInitialized;
externalAccessDesc.usage = static_cast<WGPUTextureUsageFlags>(dawnDescriptor.usage);
if (fenceSharedHandle != nullptr) {
externalAccessDesc.waitFences.push_back({fenceSharedHandle, kFenceSignalValue});
externalAccessDesc.waitFences.push_back({fenceSharedHandle, fenceWaitValue});
}
*dawnTextureOut = wgpu::Texture::Acquire(externalImage->BeginAccess(&externalAccessDesc));
*d3d11TextureOut = d3d11Texture.Detach();
*externalImageOut = std::move(externalImage);
}
void WrapAndClearD3D11Texture(
const wgpu::TextureDescriptor& dawnDescriptor,
const D3D11_TEXTURE2D_DESC& d3dDescriptor,
const wgpu::Color& clearColor,
wgpu::Texture* dawnTextureOut,
ID3D11Texture2D** d3d11TextureOut,
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI>* externalImageOut,
bool isInitialized = true) const {
ComPtr<ID3D11Texture2D> d3d11Texture;
ComPtr<ID3D11Fence> d3d11Fence;
HANDLE sharedHandle = nullptr;
HANDLE fenceSharedHandle = nullptr;
CreateSharedD3D11Texture(d3dDescriptor, &d3d11Texture, &d3d11Fence, &sharedHandle,
&fenceSharedHandle);
constexpr uint64_t kFenceSignalValue = 1;
ClearD3D11Texture(clearColor, d3d11Texture.Get(), d3d11Fence.Get(), kFenceSignalValue);
WaitAndWrapD3D11Texture(dawnDescriptor, sharedHandle, fenceSharedHandle,
/*fenceWaitValue=*/kFenceSignalValue, dawnTextureOut,
externalImageOut, isInitialized);
*d3d11TextureOut = d3d11Texture.Detach();
if (fenceSharedHandle != nullptr) {
::CloseHandle(fenceSharedHandle);
@ -1123,6 +1170,69 @@ TEST_P(D3D12SharedHandleMultithreadTests, DestroyDeviceAndUseImageInParallel) {
thread2.join();
}
// 1. Create and clear a D3D11 texture
// 2. On 2nd thread: Wrap it in a Dawn texture and clear it to a different color
// 3. Readback the texture with D3D11 and ensure we receive the color we cleared with Dawn.
TEST_P(D3D12SharedHandleMultithreadTests, ClearInD3D12ReadbackInD3D11_TwoThreads) {
// TODO(crbug.com/dawn/735): This test appears to hang for
// D3D12_Microsoft_Basic_Render_Driver_CPU when validation is enabled.
DAWN_SUPPRESS_TEST_IF(IsD3D12() && IsWARP() && IsBackendValidationEnabled());
// KeyedMutex doesn't guarantee the order of commands so skip it.
DAWN_TEST_UNSUPPORTED_IF(GetParam().mSyncMode != SyncMode::kFence);
const wgpu::Color d3d11ClearColor{1.0f, 1.0f, 0.0f, 1.0f};
const wgpu::Color d3d12ClearColor{0.0f, 0.0f, 1.0f, 1.0f};
constexpr uint64_t kD3D11FenceSignalValue = 1;
ComPtr<ID3D11Texture2D> d3d11Texture;
ComPtr<ID3D11Fence> d3d11Fence;
HANDLE sharedHandle = nullptr;
HANDLE fenceSharedHandle = nullptr;
CreateSharedD3D11Texture(baseD3dDescriptor, &d3d11Texture, &d3d11Fence, &sharedHandle,
&fenceSharedHandle);
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor d3d12SignalFence;
std::thread d3d12Thread([=, &d3d12SignalFence] {
wgpu::Texture dawnTexture;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
WaitAndWrapD3D11Texture(baseDawnDescriptor, sharedHandle, fenceSharedHandle,
/*fenceWaitValue=*/kD3D11FenceSignalValue, &dawnTexture,
&externalImage, /*isInitialized=*/true);
ASSERT_NE(dawnTexture.Get(), nullptr);
EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(d3d11ClearColor.r * 255, d3d11ClearColor.g * 255,
d3d11ClearColor.b * 255, d3d11ClearColor.a * 255),
dawnTexture, 0, 0);
ClearImage(dawnTexture, d3d12ClearColor, device);
externalImage->EndAccess(dawnTexture.Get(), &d3d12SignalFence);
dawnTexture.Destroy();
});
ClearD3D11Texture(d3d11ClearColor, d3d11Texture.Get(), d3d11Fence.Get(),
/*fenceSignalValue=*/kD3D11FenceSignalValue);
d3d12Thread.join();
// Now that Dawn (via D3D12) has finished writing to the texture, we should be
// able to read it back by copying it to a staging texture and verifying the
// color matches the D3D12 clear color.
ExpectPixelRGBA8EQ(d3d11Texture.Get(), d3d12ClearColor, &d3d12SignalFence);
if (sharedHandle != nullptr) {
::CloseHandle(sharedHandle);
}
if (fenceSharedHandle != nullptr) {
::CloseHandle(fenceSharedHandle);
}
}
DAWN_INSTANTIATE_TEST_P(D3D12SharedHandleValidation,
{D3D12Backend()},
{SyncMode::kKeyedMutex, SyncMode::kFence});

View File

@ -211,6 +211,47 @@ TEST_P(DeviceLifetimeTests, DroppedThenMapBuffer) {
}
}
// Test that the device can be dropped before a buffer created from it, then mapping the buffer
// twice (one inside callback) will both fail.
TEST_P(DeviceLifetimeTests, Dropped_ThenMapBuffer_ThenMapBufferInCallback) {
wgpu::BufferDescriptor desc = {};
desc.size = 4;
desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&desc);
device = nullptr;
struct UserData {
wgpu::Buffer buffer;
bool done = false;
};
UserData userData;
userData.buffer = buffer;
// First mapping.
buffer.MapAsync(
wgpu::MapMode::Read, 0, wgpu::kWholeMapSize,
[](WGPUBufferMapAsyncStatus status, void* userdataPtr) {
EXPECT_EQ(status, WGPUBufferMapAsyncStatus_DeviceLost);
auto userdata = static_cast<UserData*>(userdataPtr);
// Second mapping.
userdata->buffer.MapAsync(
wgpu::MapMode::Read, 0, wgpu::kWholeMapSize,
[](WGPUBufferMapAsyncStatus status, void* userdataPtr) {
EXPECT_EQ(status, WGPUBufferMapAsyncStatus_DeviceLost);
*static_cast<bool*>(userdataPtr) = true;
},
&userdata->done);
},
&userData);
while (!userData.done) {
WaitABit();
}
}
// Test that the device can be dropped inside a buffer map callback.
TEST_P(DeviceLifetimeTests, DroppedInsideBufferMapCallback) {
wgpu::BufferDescriptor desc = {};

View File

@ -16,6 +16,10 @@
#include <CoreVideo/CVPixelBuffer.h>
#include <IOSurface/IOSurface.h>
#include <memory>
#include <thread>
#include <vector>
#include "dawn/tests/DawnTest.h"
#include "dawn/native/MetalBackend.h"
@ -588,5 +592,66 @@ TEST_P(IOSurfaceUsageTests, WriteThenConcurrentReadThenWrite) {
EXPECT_TRUE(endWriteAccessDesc.isInitialized);
}
class IOSurfaceMultithreadTests : public IOSurfaceUsageTests {
protected:
std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
std::vector<wgpu::FeatureName> features;
// TODO(crbug.com/dawn/1678): DawnWire doesn't support thread safe API yet.
if (!UsesWire()) {
features.push_back(wgpu::FeatureName::ImplicitDeviceSynchronization);
}
return features;
}
void SetUp() override {
IOSurfaceUsageTests::SetUp();
// TODO(crbug.com/dawn/1678): DawnWire doesn't support thread safe API yet.
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
}
};
// Test that texture with color is cleared when isInitialized = false. There shoudn't be any data
// race if multiple of them are created on multiple threads.
TEST_P(IOSurfaceMultithreadTests, UninitializedTexturesAreCleared_OnMultipleThreads) {
utils::RunInParallel(10, [this](uint32_t) {
ScopedIOSurfaceRef ioSurface =
CreateSinglePlaneIOSurface(1, 1, kCVPixelFormatType_32RGBA, 4);
uint32_t data = 0x04030201;
IOSurfaceLock(ioSurface.get(), 0, nullptr);
memcpy(IOSurfaceGetBaseAddress(ioSurface.get()), &data, sizeof(data));
IOSurfaceUnlock(ioSurface.get(), 0, nullptr);
wgpu::TextureDescriptor textureDescriptor;
textureDescriptor.dimension = wgpu::TextureDimension::e2D;
textureDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
textureDescriptor.size = {1, 1, 1};
textureDescriptor.sampleCount = 1;
textureDescriptor.mipLevelCount = 1;
textureDescriptor.usage =
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
// wrap ioSurface and ensure color is not visible when isInitialized set to false
wgpu::Texture ioSurfaceTexture = WrapIOSurface(&textureDescriptor, ioSurface.get(), false);
EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0, 0, 0, 0), ioSurfaceTexture, 0, 0);
dawn::native::metal::ExternalImageIOSurfaceEndAccessDescriptor endAccessDesc;
dawn::native::metal::IOSurfaceEndAccess(ioSurfaceTexture.Get(), &endAccessDesc);
EXPECT_TRUE(endAccessDesc.isInitialized);
});
}
// Test that wrapping multiple IOSurface and clear them on multiple threads work.
TEST_P(IOSurfaceMultithreadTests, WrapAndClear_OnMultipleThreads) {
utils::RunInParallel(10, [this](uint32_t) {
ScopedIOSurfaceRef ioSurface =
CreateSinglePlaneIOSurface(1, 1, kCVPixelFormatType_32BGRA, 4);
uint32_t data = 0x04010203;
DoClearTest(ioSurface.get(), wgpu::TextureFormat::BGRA8Unorm, &data, sizeof(data));
});
}
DAWN_INSTANTIATE_TEST(IOSurfaceValidationTests, MetalBackend());
DAWN_INSTANTIATE_TEST(IOSurfaceUsageTests, MetalBackend());
DAWN_INSTANTIATE_TEST(IOSurfaceMultithreadTests, MetalBackend());

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,9 @@
// limitations under the License.
#include <algorithm>
#include <memory>
#include <ostream>
#include <thread>
#include <vector>
#include "dawn/common/Assert.h"
@ -190,4 +192,22 @@ uint32_t VertexFormatSize(wgpu::VertexFormat format) {
UNREACHABLE();
}
void RunInParallel(uint32_t numThreads,
const std::function<void(uint32_t)>& workerFunc,
const std::function<void()>& mainThreadFunc) {
std::vector<std::unique_ptr<std::thread>> threads(numThreads);
for (uint32_t i = 0; i < threads.size(); ++i) {
threads[i] = std::make_unique<std::thread>([i, workerFunc] { workerFunc(i); });
}
if (mainThreadFunc != nullptr) {
mainThreadFunc();
}
for (auto& thread : threads) {
thread->join();
}
}
} // namespace utils

View File

@ -15,6 +15,7 @@
#ifndef SRC_DAWN_UTILS_TESTUTILS_H_
#define SRC_DAWN_UTILS_TESTUTILS_H_
#include <functional>
#include <ostream>
#include "dawn/webgpu_cpp.h"
@ -84,6 +85,10 @@ void UnalignDynamicUploader(wgpu::Device device);
uint32_t VertexFormatSize(wgpu::VertexFormat format);
void RunInParallel(uint32_t numThreads,
const std::function<void(uint32_t)>& workerFunc,
const std::function<void()>& mainThreadFunc = nullptr);
} // namespace utils
#endif // SRC_DAWN_UTILS_TESTUTILS_H_