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:
parent
653e99478e
commit
23f4396177
|
@ -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();
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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 = {};
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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_
|
||||
|
|
Loading…
Reference in New Issue