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 "dawn/tests/DawnTest.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
@ -1146,6 +1147,9 @@ std::ostringstream& DawnTestBase::AddBufferExpectation(const char* file,
|
||||||
deferred.size = size;
|
deferred.size = size;
|
||||||
deferred.expectation.reset(expectation);
|
deferred.expectation.reset(expectation);
|
||||||
|
|
||||||
|
// This expectation might be called from multiple threads
|
||||||
|
dawn::Mutex::AutoLock lg(&mMutex);
|
||||||
|
|
||||||
mDeferredExpectations.push_back(std::move(deferred));
|
mDeferredExpectations.push_back(std::move(deferred));
|
||||||
mDeferredExpectations.back().message = std::make_unique<std::ostringstream>();
|
mDeferredExpectations.back().message = std::make_unique<std::ostringstream>();
|
||||||
return *(mDeferredExpectations.back().message.get());
|
return *(mDeferredExpectations.back().message.get());
|
||||||
|
@ -1200,6 +1204,9 @@ std::ostringstream& DawnTestBase::AddTextureExpectationImpl(const char* file,
|
||||||
deferred.bytesPerRow = bytesPerRow;
|
deferred.bytesPerRow = bytesPerRow;
|
||||||
deferred.expectation.reset(expectation);
|
deferred.expectation.reset(expectation);
|
||||||
|
|
||||||
|
// This expectation might be called from multiple threads
|
||||||
|
dawn::Mutex::AutoLock lg(&mMutex);
|
||||||
|
|
||||||
mDeferredExpectations.push_back(std::move(deferred));
|
mDeferredExpectations.push_back(std::move(deferred));
|
||||||
mDeferredExpectations.back().message = std::make_unique<std::ostringstream>();
|
mDeferredExpectations.back().message = std::make_unique<std::ostringstream>();
|
||||||
return *(mDeferredExpectations.back().message.get());
|
return *(mDeferredExpectations.back().message.get());
|
||||||
|
@ -1504,11 +1511,16 @@ void DawnTestBase::FlushWire() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DawnTestBase::WaitForAllOperations() {
|
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(
|
device.GetQueue().OnSubmittedWorkDone(
|
||||||
0u, [](WGPUQueueWorkDoneStatus, void* userdata) { *static_cast<bool*>(userdata) = true; },
|
0u,
|
||||||
|
[](WGPUQueueWorkDoneStatus, void* userdata) {
|
||||||
|
*static_cast<std::atomic<bool>*>(userdata) = true;
|
||||||
|
},
|
||||||
&done);
|
&done);
|
||||||
while (!done) {
|
while (!done.load()) {
|
||||||
WaitABit();
|
WaitABit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1526,6 +1538,9 @@ DawnTestBase::ReadbackReservation DawnTestBase::ReserveReadback(wgpu::Device tar
|
||||||
utils::CreateBufferFromData(targetDevice, initialBufferData.data(), readbackSize,
|
utils::CreateBufferFromData(targetDevice, initialBufferData.data(), readbackSize,
|
||||||
wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst);
|
wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst);
|
||||||
|
|
||||||
|
// This readback might be called from multiple threads
|
||||||
|
dawn::Mutex::AutoLock lg(&mMutex);
|
||||||
|
|
||||||
ReadbackReservation reservation;
|
ReadbackReservation reservation;
|
||||||
reservation.device = targetDevice;
|
reservation.device = targetDevice;
|
||||||
reservation.buffer = slot.buffer;
|
reservation.buffer = slot.buffer;
|
||||||
|
@ -1551,7 +1566,7 @@ void DawnTestBase::MapSlotsSynchronously() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Busy wait until all map operations are done.
|
// Busy wait until all map operations are done.
|
||||||
while (mNumPendingMapOperations != 0) {
|
while (mNumPendingMapOperations.load(std::memory_order_acquire) != 0) {
|
||||||
WaitABit();
|
WaitABit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1562,7 +1577,8 @@ void DawnTestBase::SlotMapCallback(WGPUBufferMapAsyncStatus status, void* userda
|
||||||
status == WGPUBufferMapAsyncStatus_DeviceLost);
|
status == WGPUBufferMapAsyncStatus_DeviceLost);
|
||||||
std::unique_ptr<MapReadUserdata> userdata(static_cast<MapReadUserdata*>(userdata_));
|
std::unique_ptr<MapReadUserdata> userdata(static_cast<MapReadUserdata*>(userdata_));
|
||||||
DawnTestBase* test = userdata->test;
|
DawnTestBase* test = userdata->test;
|
||||||
test->mNumPendingMapOperations--;
|
|
||||||
|
dawn::Mutex::AutoLock lg(&test->mMutex);
|
||||||
|
|
||||||
ReadbackSlot* slot = &test->mReadbackSlots[userdata->slot];
|
ReadbackSlot* slot = &test->mReadbackSlots[userdata->slot];
|
||||||
if (status == WGPUBufferMapAsyncStatus_Success) {
|
if (status == WGPUBufferMapAsyncStatus_Success) {
|
||||||
|
@ -1571,6 +1587,8 @@ void DawnTestBase::SlotMapCallback(WGPUBufferMapAsyncStatus status, void* userda
|
||||||
} else {
|
} else {
|
||||||
slot->mappedData = nullptr;
|
slot->mappedData = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test->mNumPendingMapOperations.fetch_sub(1, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DawnTestBase::ResolveExpectations() {
|
void DawnTestBase::ResolveExpectations() {
|
||||||
|
@ -1629,6 +1647,8 @@ void DawnTestBase::ResolveDeferredExpectationsNow() {
|
||||||
FlushWire();
|
FlushWire();
|
||||||
|
|
||||||
MapSlotsSynchronously();
|
MapSlotsSynchronously();
|
||||||
|
|
||||||
|
dawn::Mutex::AutoLock lg(&mMutex);
|
||||||
ResolveExpectations();
|
ResolveExpectations();
|
||||||
|
|
||||||
mDeferredExpectations.clear();
|
mDeferredExpectations.clear();
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#ifndef SRC_DAWN_TESTS_DAWNTEST_H_
|
#ifndef SRC_DAWN_TESTS_DAWNTEST_H_
|
||||||
#define SRC_DAWN_TESTS_DAWNTEST_H_
|
#define SRC_DAWN_TESTS_DAWNTEST_H_
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "dawn/common/Log.h"
|
#include "dawn/common/Log.h"
|
||||||
|
#include "dawn/common/Mutex.h"
|
||||||
#include "dawn/common/Platform.h"
|
#include "dawn/common/Platform.h"
|
||||||
#include "dawn/common/Preprocessor.h"
|
#include "dawn/common/Preprocessor.h"
|
||||||
#include "dawn/dawn_proc_table.h"
|
#include "dawn/dawn_proc_table.h"
|
||||||
|
@ -623,7 +625,7 @@ class DawnTestBase {
|
||||||
// Maps all the buffers and fill ReadbackSlot::mappedData
|
// Maps all the buffers and fill ReadbackSlot::mappedData
|
||||||
void MapSlotsSynchronously();
|
void MapSlotsSynchronously();
|
||||||
static void SlotMapCallback(WGPUBufferMapAsyncStatus status, void* userdata);
|
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
|
// Reserve space where the data for an expectation can be copied
|
||||||
struct ReadbackReservation {
|
struct ReadbackReservation {
|
||||||
|
@ -656,6 +658,8 @@ class DawnTestBase {
|
||||||
WGPUDevice mLastCreatedBackendDevice;
|
WGPUDevice mLastCreatedBackendDevice;
|
||||||
|
|
||||||
std::unique_ptr<dawn::platform::Platform> mTestPlatform;
|
std::unique_ptr<dawn::platform::Platform> mTestPlatform;
|
||||||
|
|
||||||
|
dawn::Mutex mMutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define DAWN_SKIP_TEST_IF_BASE(condition, type, reason) \
|
#define DAWN_SKIP_TEST_IF_BASE(condition, type, reason) \
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include <wrl/client.h>
|
#include <wrl/client.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -395,14 +396,11 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
|
||||||
queue.Submit(1, &commands);
|
queue.Submit(1, &commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WrapAndClearD3D11Texture(
|
void CreateSharedD3D11Texture(const D3D11_TEXTURE2D_DESC& d3dDescriptor,
|
||||||
const wgpu::TextureDescriptor& dawnDescriptor,
|
|
||||||
const D3D11_TEXTURE2D_DESC& d3dDescriptor,
|
|
||||||
const wgpu::Color& clearColor,
|
|
||||||
wgpu::Texture* dawnTextureOut,
|
|
||||||
ID3D11Texture2D** d3d11TextureOut,
|
ID3D11Texture2D** d3d11TextureOut,
|
||||||
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI>* externalImageOut,
|
ID3D11Fence** d3d11FenceOut,
|
||||||
bool isInitialized = true) const {
|
HANDLE* sharedHandleOut,
|
||||||
|
HANDLE* fenceSharedHandleOut) const {
|
||||||
ComPtr<ID3D11Texture2D> d3d11Texture;
|
ComPtr<ID3D11Texture2D> d3d11Texture;
|
||||||
HRESULT hr = mD3d11Device->CreateTexture2D(&d3dDescriptor, nullptr, &d3d11Texture);
|
HRESULT hr = mD3d11Device->CreateTexture2D(&d3dDescriptor, nullptr, &d3d11Texture);
|
||||||
ASSERT_EQ(hr, S_OK);
|
ASSERT_EQ(hr, S_OK);
|
||||||
|
@ -417,20 +415,10 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
|
||||||
&sharedHandle);
|
&sharedHandle);
|
||||||
ASSERT_EQ(hr, S_OK);
|
ASSERT_EQ(hr, S_OK);
|
||||||
|
|
||||||
ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
|
|
||||||
|
|
||||||
HANDLE fenceSharedHandle = nullptr;
|
HANDLE fenceSharedHandle = nullptr;
|
||||||
ComPtr<ID3D11Fence> d3d11Fence;
|
ComPtr<ID3D11Fence> d3d11Fence;
|
||||||
|
|
||||||
ComPtr<ID3D11DeviceContext4> d3d11DeviceContext4;
|
if (GetParam().mSyncMode == SyncMode::kFence) {
|
||||||
|
|
||||||
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 {
|
|
||||||
ComPtr<ID3D11Device5> d3d11Device5;
|
ComPtr<ID3D11Device5> d3d11Device5;
|
||||||
hr = mD3d11Device.As(&d3d11Device5);
|
hr = mD3d11Device.As(&d3d11Device5);
|
||||||
ASSERT_EQ(hr, S_OK);
|
ASSERT_EQ(hr, S_OK);
|
||||||
|
@ -442,6 +430,33 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
|
||||||
ASSERT_EQ(hr, S_OK);
|
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;
|
ComPtr<ID3D11RenderTargetView> d3d11RTV;
|
||||||
hr = mD3d11Device->CreateRenderTargetView(d3d11Texture.Get(), nullptr, &d3d11RTV);
|
hr = mD3d11Device->CreateRenderTargetView(d3d11Texture.Get(), nullptr, &d3d11RTV);
|
||||||
ASSERT_EQ(hr, S_OK);
|
ASSERT_EQ(hr, S_OK);
|
||||||
|
@ -451,7 +466,6 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
|
||||||
static_cast<float>(clearColor.b), static_cast<float>(clearColor.a)};
|
static_cast<float>(clearColor.b), static_cast<float>(clearColor.a)};
|
||||||
mD3d11DeviceContext->ClearRenderTargetView(d3d11RTV.Get(), colorRGBA);
|
mD3d11DeviceContext->ClearRenderTargetView(d3d11RTV.Get(), colorRGBA);
|
||||||
|
|
||||||
constexpr uint64_t kFenceSignalValue = 1;
|
|
||||||
if (dxgiKeyedMutex) {
|
if (dxgiKeyedMutex) {
|
||||||
hr = dxgiKeyedMutex->ReleaseSync(kDXGIKeyedMutexAcquireReleaseKey);
|
hr = dxgiKeyedMutex->ReleaseSync(kDXGIKeyedMutexAcquireReleaseKey);
|
||||||
ASSERT_EQ(hr, S_OK);
|
ASSERT_EQ(hr, S_OK);
|
||||||
|
@ -460,9 +474,18 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
|
||||||
ASSERT_EQ(hr, S_OK);
|
ASSERT_EQ(hr, S_OK);
|
||||||
// The fence starts with 0 signaled, but that won't capture the render target view clear
|
// 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.
|
// 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 = {};
|
dawn::native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc = {};
|
||||||
externalImageDesc.sharedHandle = sharedHandle;
|
externalImageDesc.sharedHandle = sharedHandle;
|
||||||
externalImageDesc.cTextureDescriptor =
|
externalImageDesc.cTextureDescriptor =
|
||||||
|
@ -476,12 +499,36 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
|
||||||
externalAccessDesc.isInitialized = isInitialized;
|
externalAccessDesc.isInitialized = isInitialized;
|
||||||
externalAccessDesc.usage = static_cast<WGPUTextureUsageFlags>(dawnDescriptor.usage);
|
externalAccessDesc.usage = static_cast<WGPUTextureUsageFlags>(dawnDescriptor.usage);
|
||||||
if (fenceSharedHandle != nullptr) {
|
if (fenceSharedHandle != nullptr) {
|
||||||
externalAccessDesc.waitFences.push_back({fenceSharedHandle, kFenceSignalValue});
|
externalAccessDesc.waitFences.push_back({fenceSharedHandle, fenceWaitValue});
|
||||||
}
|
}
|
||||||
|
|
||||||
*dawnTextureOut = wgpu::Texture::Acquire(externalImage->BeginAccess(&externalAccessDesc));
|
*dawnTextureOut = wgpu::Texture::Acquire(externalImage->BeginAccess(&externalAccessDesc));
|
||||||
*d3d11TextureOut = d3d11Texture.Detach();
|
|
||||||
*externalImageOut = std::move(externalImage);
|
*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) {
|
if (fenceSharedHandle != nullptr) {
|
||||||
::CloseHandle(fenceSharedHandle);
|
::CloseHandle(fenceSharedHandle);
|
||||||
|
@ -1123,6 +1170,69 @@ TEST_P(D3D12SharedHandleMultithreadTests, DestroyDeviceAndUseImageInParallel) {
|
||||||
thread2.join();
|
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,
|
DAWN_INSTANTIATE_TEST_P(D3D12SharedHandleValidation,
|
||||||
{D3D12Backend()},
|
{D3D12Backend()},
|
||||||
{SyncMode::kKeyedMutex, SyncMode::kFence});
|
{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 that the device can be dropped inside a buffer map callback.
|
||||||
TEST_P(DeviceLifetimeTests, DroppedInsideBufferMapCallback) {
|
TEST_P(DeviceLifetimeTests, DroppedInsideBufferMapCallback) {
|
||||||
wgpu::BufferDescriptor desc = {};
|
wgpu::BufferDescriptor desc = {};
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
#include <CoreVideo/CVPixelBuffer.h>
|
#include <CoreVideo/CVPixelBuffer.h>
|
||||||
#include <IOSurface/IOSurface.h>
|
#include <IOSurface/IOSurface.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "dawn/tests/DawnTest.h"
|
#include "dawn/tests/DawnTest.h"
|
||||||
|
|
||||||
#include "dawn/native/MetalBackend.h"
|
#include "dawn/native/MetalBackend.h"
|
||||||
|
@ -588,5 +592,66 @@ TEST_P(IOSurfaceUsageTests, WriteThenConcurrentReadThenWrite) {
|
||||||
EXPECT_TRUE(endWriteAccessDesc.isInitialized);
|
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(IOSurfaceValidationTests, MetalBackend());
|
||||||
DAWN_INSTANTIATE_TEST(IOSurfaceUsageTests, 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.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "dawn/common/Assert.h"
|
#include "dawn/common/Assert.h"
|
||||||
|
@ -190,4 +192,22 @@ uint32_t VertexFormatSize(wgpu::VertexFormat format) {
|
||||||
UNREACHABLE();
|
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
|
} // namespace utils
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#ifndef SRC_DAWN_UTILS_TESTUTILS_H_
|
#ifndef SRC_DAWN_UTILS_TESTUTILS_H_
|
||||||
#define SRC_DAWN_UTILS_TESTUTILS_H_
|
#define SRC_DAWN_UTILS_TESTUTILS_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
|
||||||
#include "dawn/webgpu_cpp.h"
|
#include "dawn/webgpu_cpp.h"
|
||||||
|
@ -84,6 +85,10 @@ void UnalignDynamicUploader(wgpu::Device device);
|
||||||
|
|
||||||
uint32_t VertexFormatSize(wgpu::VertexFormat format);
|
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
|
} // namespace utils
|
||||||
|
|
||||||
#endif // SRC_DAWN_UTILS_TESTUTILS_H_
|
#endif // SRC_DAWN_UTILS_TESTUTILS_H_
|
||||||
|
|
Loading…
Reference in New Issue