dawn-cmake/src/dawn/tests/DawnTest.h
Le Hoang Quyen 8ab7dbe424 DawnNative: Defer callbacks' triggerings to APITick().
Currently in the middle of some functions, we execute callbacks
immediately such as inside Buffer::APIMapAsync(), Device::HandleError()
or Queue::Submit().

Firstly, this has risks. The functions might be in a middle of modifying
internal states. By triggering callbacks, users might call API
functions again which could further modify the internal states
unexpectedly or access the states in an inconsistent way.

Secondly, upcoming thread safe API which locks the public functions with
a mutex might encounter deadlock. Because callbacks might cause
re-entrances which would unexpectedly lock the public function again.

This CL attempts to limit number of functions that are allowed to
trigger callbacks. Other functions that want to trigger callbacks will
instead enqueue a request to execute callbacks in the next
Device::APITick() call.

Currently the functions that will be allowed to trigger callbacks are:
- Device::WillDropLastExternalRef()
- Device::APITick()
- Device::APISetLoggingCallback()
- Device::APISetUncapturedErrorCallback()
- Device::APISetDeviceLostCallback()

Bug: dawn:1672
Change-Id: Iabca00f1b6f8f69eb5e966ffaa43dda5ae20fa8b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/120940
Commit-Queue: Quyen Le <lehoangquyen@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
2023-03-06 19:03:26 +00:00

857 lines
40 KiB
C++

// Copyright 2017 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.
#ifndef SRC_DAWN_TESTS_DAWNTEST_H_
#define SRC_DAWN_TESTS_DAWNTEST_H_
#include <memory>
#include <queue>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "dawn/common/Log.h"
#include "dawn/common/Platform.h"
#include "dawn/common/Preprocessor.h"
#include "dawn/dawn_proc_table.h"
#include "dawn/native/DawnNative.h"
#include "dawn/platform/DawnPlatform.h"
#include "dawn/tests/AdapterTestConfig.h"
#include "dawn/tests/MockCallback.h"
#include "dawn/tests/ParamGenerator.h"
#include "dawn/tests/ToggleParser.h"
#include "dawn/utils/ScopedAutoreleasePool.h"
#include "dawn/utils/TestUtils.h"
#include "dawn/utils/TextureUtils.h"
#include "dawn/webgpu_cpp.h"
#include "dawn/webgpu_cpp_print.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
// Getting data back from Dawn is done in an async manners so all expectations are "deferred"
// until the end of the test. Also expectations use a copy to a MapRead buffer to get the data
// so resources should have the CopySrc allowed usage bit if you want to add expectations on
// them.
// AddBufferExpectation is defined in DawnTestBase as protected function. This ensures the macro can
// only be used in derivd class of DawnTestBase. Use "this" pointer to ensure the macro works with
// CRTP.
#define EXPECT_BUFFER(buffer, offset, size, expectation) \
this->AddBufferExpectation(__FILE__, __LINE__, buffer, offset, size, expectation)
#define EXPECT_BUFFER_U8_EQ(expected, buffer, offset) \
EXPECT_BUFFER(buffer, offset, sizeof(uint8_t), new ::detail::ExpectEq<uint8_t>(expected))
#define EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, offset, count) \
EXPECT_BUFFER(buffer, offset, sizeof(uint8_t) * (count), \
new ::detail::ExpectEq<uint8_t>(expected, count))
#define EXPECT_BUFFER_U16_EQ(expected, buffer, offset) \
EXPECT_BUFFER(buffer, offset, sizeof(uint16_t), new ::detail::ExpectEq<uint16_t>(expected))
#define EXPECT_BUFFER_U16_RANGE_EQ(expected, buffer, offset, count) \
EXPECT_BUFFER(buffer, offset, sizeof(uint16_t) * (count), \
new ::detail::ExpectEq<uint16_t>(expected, count))
#define EXPECT_BUFFER_U32_EQ(expected, buffer, offset) \
EXPECT_BUFFER(buffer, offset, sizeof(uint32_t), new ::detail::ExpectEq<uint32_t>(expected))
#define EXPECT_BUFFER_U32_RANGE_EQ(expected, buffer, offset, count) \
EXPECT_BUFFER(buffer, offset, sizeof(uint32_t) * (count), \
new ::detail::ExpectEq<uint32_t>(expected, count))
#define EXPECT_BUFFER_U64_EQ(expected, buffer, offset) \
EXPECT_BUFFER(buffer, offset, sizeof(uint64_t), new ::detail::ExpectEq<uint64_t>(expected))
#define EXPECT_BUFFER_U64_RANGE_EQ(expected, buffer, offset, count) \
EXPECT_BUFFER(buffer, offset, sizeof(uint64_t) * (count), \
new ::detail::ExpectEq<uint64_t>(expected, count))
#define EXPECT_BUFFER_FLOAT_EQ(expected, buffer, offset) \
EXPECT_BUFFER(buffer, offset, sizeof(float), new ::detail::ExpectEq<float>(expected))
#define EXPECT_BUFFER_FLOAT_RANGE_EQ(expected, buffer, offset, count) \
EXPECT_BUFFER(buffer, offset, sizeof(float) * (count), \
new ::detail::ExpectEq<float>(expected, count))
// Test a pixel of the mip level 0 of a 2D texture.
#define EXPECT_PIXEL_RGBA8_EQ(expected, texture, x, y) \
AddTextureExpectation(__FILE__, __LINE__, expected, texture, {x, y})
#define EXPECT_PIXEL_FLOAT_EQ(expected, texture, x, y) \
AddTextureExpectation(__FILE__, __LINE__, expected, texture, {x, y})
#define EXPECT_PIXEL_FLOAT16_EQ(expected, texture, x, y) \
AddTextureExpectation<float, uint16_t>(__FILE__, __LINE__, expected, texture, {x, y})
#define EXPECT_PIXEL_RGBA8_BETWEEN(color0, color1, texture, x, y) \
AddTextureBetweenColorsExpectation(__FILE__, __LINE__, color0, color1, texture, x, y)
#define EXPECT_TEXTURE_EQ(...) AddTextureExpectation(__FILE__, __LINE__, __VA_ARGS__)
#define EXPECT_TEXTURE_FLOAT16_EQ(...) \
AddTextureExpectation<float, uint16_t>(__FILE__, __LINE__, __VA_ARGS__)
#define ASSERT_DEVICE_ERROR_MSG_ON(device, statement, matcher) \
FlushWire(); \
EXPECT_CALL(mDeviceErrorCallback, \
Call(testing::Ne(WGPUErrorType_NoError), matcher, device.Get())); \
statement; \
device.Tick(); \
FlushWire(); \
testing::Mock::VerifyAndClearExpectations(&mDeviceErrorCallback); \
do { \
} while (0)
#define ASSERT_DEVICE_ERROR_MSG(statement, matcher) \
ASSERT_DEVICE_ERROR_MSG_ON(this->device, statement, matcher)
#define ASSERT_DEVICE_ERROR_ON(device, statement) \
ASSERT_DEVICE_ERROR_MSG_ON(device, statement, testing::_)
#define ASSERT_DEVICE_ERROR(statement) ASSERT_DEVICE_ERROR_MSG(statement, testing::_)
struct GLFWwindow;
namespace utils {
class PlatformDebugLogger;
class TerribleCommandBuffer;
class WireHelper;
} // namespace utils
namespace detail {
class Expectation;
class CustomTextureExpectation;
template <typename T>
class ExpectConstant;
template <typename T, typename U = T>
class ExpectEq;
template <typename T>
class ExpectBetweenColors;
} // namespace detail
namespace dawn::wire {
class CommandHandler;
class WireClient;
class WireServer;
} // namespace dawn::wire
void InitDawnEnd2EndTestEnvironment(int argc, char** argv);
class DawnTestEnvironment : public testing::Environment {
public:
DawnTestEnvironment(int argc, char** argv);
~DawnTestEnvironment() override;
static void SetEnvironment(DawnTestEnvironment* env);
std::vector<AdapterTestParam> GetAvailableAdapterTestParamsForBackends(
const BackendTestConfig* params,
size_t numParams);
void SetUp() override;
void TearDown() override;
bool UsesWire() const;
dawn::native::BackendValidationLevel GetBackendValidationLevel() const;
dawn::native::Instance* GetInstance() const;
bool HasVendorIdFilter() const;
uint32_t GetVendorIdFilter() const;
bool HasBackendTypeFilter() const;
wgpu::BackendType GetBackendTypeFilter() const;
const char* GetWireTraceDir() const;
const std::vector<std::string>& GetEnabledToggles() const;
const std::vector<std::string>& GetDisabledToggles() const;
bool RunSuppressedTests() const;
protected:
std::unique_ptr<dawn::native::Instance> mInstance;
private:
void ParseArgs(int argc, char** argv);
std::unique_ptr<dawn::native::Instance> CreateInstanceAndDiscoverAdapters();
void SelectPreferredAdapterProperties(const dawn::native::Instance* instance);
void PrintTestConfigurationAndAdapterInfo(dawn::native::Instance* instance) const;
/// @returns true if all the toggles are recognised, otherwise prints an error and returns
/// false.
bool ValidateToggles(dawn::native::Instance* instance) const;
bool mUseWire = false;
dawn::native::BackendValidationLevel mBackendValidationLevel =
dawn::native::BackendValidationLevel::Disabled;
std::string mANGLEBackend;
bool mBeginCaptureOnStartup = false;
bool mHasVendorIdFilter = false;
uint32_t mVendorIdFilter = 0;
bool mHasBackendTypeFilter = false;
wgpu::BackendType mBackendTypeFilter;
std::string mWireTraceDir;
bool mRunSuppressedTests = false;
ToggleParser mToggleParser;
std::vector<wgpu::AdapterType> mDevicePreferences;
std::vector<TestAdapterProperties> mAdapterProperties;
std::unique_ptr<utils::PlatformDebugLogger> mPlatformDebugLogger;
};
class DawnTestBase {
friend class DawnPerfTestBase;
public:
explicit DawnTestBase(const AdapterTestParam& param);
virtual ~DawnTestBase();
void SetUp();
void TearDown();
bool IsD3D12() const;
bool IsMetal() const;
bool IsNull() const;
bool IsOpenGL() const;
bool IsOpenGLES() const;
bool IsVulkan() const;
bool IsAMD() const;
bool IsApple() const;
bool IsARM() const;
bool IsImgTec() const;
bool IsIntel() const;
bool IsNvidia() const;
bool IsQualcomm() const;
bool IsSwiftshader() const;
bool IsANGLE() const;
bool IsANGLESwiftShader() const;
bool IsWARP() const;
bool IsIntelGen12() const;
bool IsWindows() const;
bool IsLinux() const;
bool IsMacOS(int32_t majorVersion = -1, int32_t minorVersion = -1) const;
bool IsAndroid() const;
bool UsesWire() const;
bool IsBackendValidationEnabled() const;
bool IsFullBackendValidationEnabled() const;
bool RunSuppressedTests() const;
bool IsDXC() const;
bool IsAsan() const;
bool HasToggleEnabled(const char* workaround) const;
void DestroyDevice(wgpu::Device device = nullptr);
void LoseDeviceForTesting(wgpu::Device device = nullptr);
bool HasVendorIdFilter() const;
uint32_t GetVendorIdFilter() const;
bool HasBackendTypeFilter() const;
wgpu::BackendType GetBackendTypeFilter() const;
wgpu::Instance GetInstance() const;
dawn::native::Adapter GetAdapter() const;
virtual std::unique_ptr<dawn::platform::Platform> CreateTestPlatform();
struct PrintToStringParamName {
explicit PrintToStringParamName(const char* test);
std::string SanitizeParamName(std::string paramName, size_t index) const;
template <class ParamType>
std::string operator()(const ::testing::TestParamInfo<ParamType>& info) const {
return SanitizeParamName(::testing::PrintToStringParamName()(info), info.index);
}
std::string mTest;
};
// Resolve all the deferred expectations in mDeferredExpectations now to avoid letting
// mDeferredExpectations get too big.
void ResolveDeferredExpectationsNow();
protected:
wgpu::Device device;
wgpu::Queue queue;
DawnProcTable backendProcs = {};
WGPUDevice backendDevice = nullptr;
size_t mLastWarningCount = 0;
// Mock callbacks tracking errors and destruction. These are strict mocks because any errors or
// device loss that aren't expected should result in test failures and not just some warnings
// printed to stdout.
testing::StrictMock<testing::MockCallback<WGPUErrorCallback>> mDeviceErrorCallback;
testing::StrictMock<testing::MockCallback<WGPUDeviceLostCallback>> mDeviceLostCallback;
// Helper methods to implement the EXPECT_ macros
std::ostringstream& AddBufferExpectation(const char* file,
int line,
const wgpu::Buffer& buffer,
uint64_t offset,
uint64_t size,
detail::Expectation* expectation);
template <typename T, typename U = T>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
const T* expectedData,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
wgpu::Extent3D extent,
wgpu::TextureFormat format,
T tolerance = 0,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
// No device passed explicitly. Default it, and forward the rest of the args.
return AddTextureExpectation<T, U>(file, line, this->device, expectedData, texture, origin,
extent, format, tolerance, level, aspect, bytesPerRow);
}
// T - expected value Type
// U - actual value Type (defaults = T)
template <typename T, typename U = T>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
wgpu::Device targetDevice,
const T* expectedData,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
wgpu::Extent3D extent,
wgpu::TextureFormat format,
T tolerance = 0,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
uint32_t texelBlockSize = utils::GetTexelBlockSizeInBytes(format);
uint32_t texelComponentCount = utils::GetWGSLRenderableColorTextureComponentCount(format);
return AddTextureExpectationImpl(
file, line, std::move(targetDevice),
new detail::ExpectEq<T, U>(
expectedData,
texelComponentCount * extent.width * extent.height * extent.depthOrArrayLayers,
tolerance),
texture, origin, extent, level, aspect, texelBlockSize, bytesPerRow);
}
template <typename T, typename U = T>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
const T* expectedData,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
wgpu::Extent3D extent,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0,
T tolerance = {}) {
// No device passed explicitly. Default it, and forward the rest of the args.
return AddTextureExpectation<T, U>(file, line, this->device, expectedData, texture, origin,
extent, level, aspect, bytesPerRow, tolerance);
}
template <typename T, typename U = T>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
wgpu::Device targetDevice,
const T* expectedData,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
wgpu::Extent3D extent,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0,
T tolerance = {}) {
return AddTextureExpectationImpl(
file, line, std::move(targetDevice),
new detail::ExpectEq<T, U>(
expectedData, extent.width * extent.height * extent.depthOrArrayLayers, tolerance),
texture, origin, extent, level, aspect, sizeof(U), bytesPerRow);
}
template <typename T, typename U = T>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
const T& expectedData,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
// No device passed explicitly. Default it, and forward the rest of the args.
return AddTextureExpectation<T, U>(file, line, this->device, expectedData, texture, origin,
level, aspect, bytesPerRow);
}
template <typename T, typename U = T>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
wgpu::Device targetDevice,
const T& expectedData,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
return AddTextureExpectationImpl(file, line, std::move(targetDevice),
new detail::ExpectEq<T, U>(expectedData), texture, origin,
{1, 1}, level, aspect, sizeof(U), bytesPerRow);
}
template <typename E,
typename = typename std::enable_if<
std::is_base_of<detail::CustomTextureExpectation, E>::value>::type>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
E* expectation,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
wgpu::Extent3D extent,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
// No device passed explicitly. Default it, and forward the rest of the args.
return AddTextureExpectation(file, line, this->device, expectation, texture, origin, extent,
level, aspect, bytesPerRow);
}
template <typename E,
typename = typename std::enable_if<
std::is_base_of<detail::CustomTextureExpectation, E>::value>::type>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
wgpu::Device targetDevice,
E* expectation,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
wgpu::Extent3D extent,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
return AddTextureExpectationImpl(file, line, std::move(targetDevice), expectation, texture,
origin, extent, level, aspect, expectation->DataSize(),
bytesPerRow);
}
template <typename T>
std::ostringstream& AddTextureBetweenColorsExpectation(
const char* file,
int line,
const T& color0,
const T& color1,
const wgpu::Texture& texture,
uint32_t x,
uint32_t y,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
// No device passed explicitly. Default it, and forward the rest of the args.
return AddTextureBetweenColorsExpectation(file, line, this->device, color0, color1, texture,
x, y, level, aspect, bytesPerRow);
}
template <typename T>
std::ostringstream& AddTextureBetweenColorsExpectation(
const char* file,
int line,
const wgpu::Device& targetDevice,
const T& color0,
const T& color1,
const wgpu::Texture& texture,
uint32_t x,
uint32_t y,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
return AddTextureExpectationImpl(
file, line, std::move(targetDevice), new detail::ExpectBetweenColors<T>(color0, color1),
texture, {x, y}, {1, 1}, level, aspect, sizeof(T), bytesPerRow);
}
std::ostringstream& ExpectSampledFloatData(wgpu::Texture texture,
uint32_t width,
uint32_t height,
uint32_t componentCount,
uint32_t arrayLayer,
uint32_t mipLevel,
detail::Expectation* expectation);
std::ostringstream& ExpectMultisampledFloatData(wgpu::Texture texture,
uint32_t width,
uint32_t height,
uint32_t componentCount,
uint32_t sampleCount,
uint32_t arrayLayer,
uint32_t mipLevel,
detail::Expectation* expectation);
std::ostringstream& ExpectSampledDepthData(wgpu::Texture depthTexture,
uint32_t width,
uint32_t height,
uint32_t arrayLayer,
uint32_t mipLevel,
detail::Expectation* expectation);
// Check depth by uploading expected data to a sampled texture, writing it out as a depth
// attachment, and then using the "equals" depth test to check the contents are the same.
// Check stencil by rendering a full screen quad and using the "equals" stencil test with
// a stencil reference value. Note that checking stencil checks that the entire stencil
// buffer is equal to the expected stencil value.
std::ostringstream& ExpectAttachmentDepthStencilTestData(wgpu::Texture texture,
wgpu::TextureFormat format,
uint32_t width,
uint32_t height,
uint32_t arrayLayer,
uint32_t mipLevel,
std::vector<float> expectedDepth,
uint8_t* expectedStencil);
std::ostringstream& ExpectAttachmentDepthTestData(wgpu::Texture texture,
wgpu::TextureFormat format,
uint32_t width,
uint32_t height,
uint32_t arrayLayer,
uint32_t mipLevel,
std::vector<float> expectedDepth) {
return ExpectAttachmentDepthStencilTestData(texture, format, width, height, arrayLayer,
mipLevel, std::move(expectedDepth), nullptr);
}
std::ostringstream& ExpectAttachmentStencilTestData(wgpu::Texture texture,
wgpu::TextureFormat format,
uint32_t width,
uint32_t height,
uint32_t arrayLayer,
uint32_t mipLevel,
uint8_t expectedStencil) {
return ExpectAttachmentDepthStencilTestData(texture, format, width, height, arrayLayer,
mipLevel, {}, &expectedStencil);
}
void WaitABit(wgpu::Device = nullptr);
void FlushWire();
void WaitForAllOperations();
bool SupportsFeatures(const std::vector<wgpu::FeatureName>& features);
// Exposed device creation helper for tests to use when needing more than 1 device.
wgpu::Device CreateDevice(std::string isolationKey = "");
// Called in SetUp() to get the features required to be enabled in the tests. The tests must
// check if the required features are supported by the adapter in this function and guarantee
// the returned features are all supported by the adapter. The tests may provide different
// code path to handle the situation when not all features are supported.
virtual std::vector<wgpu::FeatureName> GetRequiredFeatures();
virtual wgpu::RequiredLimits GetRequiredLimits(const wgpu::SupportedLimits&);
const wgpu::AdapterProperties& GetAdapterProperties() const;
wgpu::SupportedLimits GetAdapterLimits();
wgpu::SupportedLimits GetSupportedLimits();
private:
utils::ScopedAutoreleasePool mObjCAutoreleasePool;
AdapterTestParam mParam;
std::unique_ptr<utils::WireHelper> mWireHelper;
wgpu::Instance mInstance;
wgpu::Adapter mAdapter;
// Isolation keys are not exposed to the wire client. Device creation in the tests from
// the client first push the key into this queue, which is then consumed by the server.
std::queue<std::string> mNextIsolationKeyQueue;
// Internal device creation function for default device creation with some optional overrides.
WGPUDevice CreateDeviceImpl(std::string isolationKey);
std::ostringstream& AddTextureExpectationImpl(const char* file,
int line,
wgpu::Device targetDevice,
detail::Expectation* expectation,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
wgpu::Extent3D extent,
uint32_t level,
wgpu::TextureAspect aspect,
uint32_t dataSize,
uint32_t bytesPerRow);
std::ostringstream& ExpectSampledFloatDataImpl(wgpu::TextureView textureView,
const char* wgslTextureType,
uint32_t width,
uint32_t height,
uint32_t componentCount,
uint32_t sampleCount,
detail::Expectation* expectation);
// MapRead buffers used to get data for the expectations
struct ReadbackSlot {
wgpu::Device device;
wgpu::Buffer buffer;
uint64_t bufferSize;
const void* mappedData = nullptr;
};
std::vector<ReadbackSlot> mReadbackSlots;
// Maps all the buffers and fill ReadbackSlot::mappedData
void MapSlotsSynchronously();
static void SlotMapCallback(WGPUBufferMapAsyncStatus status, void* userdata);
size_t mNumPendingMapOperations = 0;
// Reserve space where the data for an expectation can be copied
struct ReadbackReservation {
wgpu::Device device;
wgpu::Buffer buffer;
size_t slot;
uint64_t offset;
};
ReadbackReservation ReserveReadback(wgpu::Device targetDevice, uint64_t readbackSize);
struct DeferredExpectation {
const char* file;
int line;
size_t readbackSlot;
uint64_t readbackOffset;
uint64_t size;
uint32_t rowBytes = 0;
uint32_t bytesPerRow = 0;
std::unique_ptr<detail::Expectation> expectation;
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54316
// Use unique_ptr because of missing move/copy constructors on std::basic_ostringstream
std::unique_ptr<std::ostringstream> message;
};
std::vector<DeferredExpectation> mDeferredExpectations;
// Assuming the data is mapped, checks all expectations
void ResolveExpectations();
dawn::native::Adapter mBackendAdapter;
WGPUDevice mLastCreatedBackendDevice;
std::unique_ptr<dawn::platform::Platform> mTestPlatform;
};
#define DAWN_SKIP_TEST_IF_BASE(condition, type, reason) \
do { \
if (condition) { \
dawn::InfoLog() << "Test " type ": " #reason; \
GTEST_SKIP(); \
return; \
} \
} while (0)
// Skip a test which requires a feature or a toggle to be present / not present or some WIP
// features.
#define DAWN_TEST_UNSUPPORTED_IF(condition) \
DAWN_SKIP_TEST_IF_BASE(condition, "unsupported", condition)
// Skip a test when the test failing on a specific HW / backend / OS combination. We can disable
// this macro with the command line parameter "--run-suppressed-tests".
#define DAWN_SUPPRESS_TEST_IF(condition) \
DAWN_SKIP_TEST_IF_BASE(!RunSuppressedTests() && condition, "suppressed", condition)
#define EXPECT_DEPRECATION_WARNINGS(statement, n) \
do { \
if (UsesWire()) { \
statement; \
} else { \
size_t warningsBefore = \
dawn::native::GetDeprecationWarningCountForTesting(device.Get()); \
statement; \
size_t warningsAfter = \
dawn::native::GetDeprecationWarningCountForTesting(device.Get()); \
EXPECT_EQ(mLastWarningCount, warningsBefore); \
if (!HasToggleEnabled("skip_validation")) { \
EXPECT_EQ(warningsAfter, warningsBefore + n); \
} \
mLastWarningCount = warningsAfter; \
} \
} while (0)
#define EXPECT_DEPRECATION_WARNING(statement) EXPECT_DEPRECATION_WARNINGS(statement, 1)
template <typename Params = AdapterTestParam>
class DawnTestWithParams : public DawnTestBase, public ::testing::TestWithParam<Params> {
protected:
DawnTestWithParams();
~DawnTestWithParams() override = default;
void SetUp() override { DawnTestBase::SetUp(); }
void TearDown() override { DawnTestBase::TearDown(); }
};
template <typename Params>
DawnTestWithParams<Params>::DawnTestWithParams() : DawnTestBase(this->GetParam()) {}
using DawnTest = DawnTestWithParams<>;
// Instantiate the test once for each backend provided after the first argument. Use it like this:
// DAWN_INSTANTIATE_TEST(MyTestFixture, MetalBackend, OpenGLBackend)
#define DAWN_INSTANTIATE_TEST(testName, ...) \
const decltype(DAWN_PP_GET_HEAD(__VA_ARGS__)) testName##params[] = {__VA_ARGS__}; \
INSTANTIATE_TEST_SUITE_P( \
, testName, \
testing::ValuesIn(::detail::GetAvailableAdapterTestParamsForBackends( \
testName##params, sizeof(testName##params) / sizeof(testName##params[0]))), \
DawnTestBase::PrintToStringParamName(#testName)); \
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(testName)
// Instantiate the test once for each backend provided in the first param list.
// The test will be parameterized over the following param lists.
// Use it like this:
// DAWN_INSTANTIATE_TEST_P(MyTestFixture, {MetalBackend(), OpenGLBackend()}, {A, B}, {1, 2})
// MyTestFixture must extend DawnTestWithParams<Param> where Param is a struct that extends
// AdapterTestParam, and whose constructor looks like:
// Param(AdapterTestParam, ABorC, 12or3, ..., otherParams... )
// You must also teach GTest how to print this struct.
// https://github.com/google/googletest/blob/main/docs/advanced.md#teaching-googletest-how-to-print-your-values
// Macro DAWN_TEST_PARAM_STRUCT can help generate this struct.
#define DAWN_INSTANTIATE_TEST_P(testName, ...) \
INSTANTIATE_TEST_SUITE_P( \
, testName, ::testing::ValuesIn(MakeParamGenerator<testName::ParamType>(__VA_ARGS__)), \
DawnTestBase::PrintToStringParamName(#testName)); \
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(testName)
// Implementation for DAWN_TEST_PARAM_STRUCT to declare/print struct fields.
#define DAWN_TEST_PARAM_STRUCT_DECL_STRUCT_FIELD(Type) Type DAWN_PP_CONCATENATE(m, Type);
#define DAWN_TEST_PARAM_STRUCT_PRINT_STRUCT_FIELD(Type) \
o << "; " << #Type << "=" << param.DAWN_PP_CONCATENATE(m, Type);
// Usage: DAWN_TEST_PARAM_STRUCT(Foo, TypeA, TypeB, ...)
// Generate a test param struct called Foo which extends AdapterTestParam and generated
// struct _Dawn_Foo. _Dawn_Foo has members of types TypeA, TypeB, etc. which are named mTypeA,
// mTypeB, etc. in the order they are placed in the macro argument list. Struct Foo should be
// constructed with an AdapterTestParam as the first argument, followed by a list of values
// to initialize the base _Dawn_Foo struct.
// It is recommended to use alias declarations so that stringified types are more readable.
// Example:
// using MyParam = unsigned int;
// DAWN_TEST_PARAM_STRUCT(FooParams, MyParam);
#define DAWN_TEST_PARAM_STRUCT(StructName, ...) \
struct DAWN_PP_CONCATENATE(_Dawn_, StructName) { \
DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH)(DAWN_TEST_PARAM_STRUCT_DECL_STRUCT_FIELD, \
__VA_ARGS__)) \
}; \
std::ostream& operator<<(std::ostream& o, \
const DAWN_PP_CONCATENATE(_Dawn_, StructName) & param) { \
DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH)(DAWN_TEST_PARAM_STRUCT_PRINT_STRUCT_FIELD, \
__VA_ARGS__)) \
return o; \
} \
struct StructName : AdapterTestParam, DAWN_PP_CONCATENATE(_Dawn_, StructName) { \
template <typename... Args> \
StructName(const AdapterTestParam& param, Args&&... args) \
: AdapterTestParam(param), \
DAWN_PP_CONCATENATE(_Dawn_, StructName){std::forward<Args>(args)...} {} \
}; \
std::ostream& operator<<(std::ostream& o, const StructName& param) { \
o << static_cast<const AdapterTestParam&>(param); \
o << "; " << static_cast<const DAWN_PP_CONCATENATE(_Dawn_, StructName)&>(param); \
return o; \
} \
static_assert(true, "require semicolon")
namespace detail {
// Helper functions used for DAWN_INSTANTIATE_TEST
std::vector<AdapterTestParam> GetAvailableAdapterTestParamsForBackends(
const BackendTestConfig* params,
size_t numParams);
// All classes used to implement the deferred expectations should inherit from this.
class Expectation {
public:
virtual ~Expectation() = default;
// Will be called with the buffer or texture data the expectation should check.
virtual testing::AssertionResult Check(const void* data, size_t size) = 0;
};
template <typename T>
class ExpectConstant : public Expectation {
public:
explicit ExpectConstant(T constant);
uint32_t DataSize();
testing::AssertionResult Check(const void* data, size_t size) override;
private:
T mConstant;
};
extern template class ExpectConstant<float>;
// Expectation that checks the data is equal to some expected values.
// T - expected value Type
// U - actual value Type (defaults = T)
// This is expanded for float16 mostly where T=float, U=uint16_t
template <typename T, typename U>
class ExpectEq : public Expectation {
public:
explicit ExpectEq(T singleValue, T tolerance = {});
ExpectEq(const T* values, const unsigned int count, T tolerance = {});
testing::AssertionResult Check(const void* data, size_t size) override;
private:
std::vector<T> mExpected;
T mTolerance;
};
extern template class ExpectEq<uint8_t>;
extern template class ExpectEq<int16_t>;
extern template class ExpectEq<uint32_t>;
extern template class ExpectEq<uint64_t>;
extern template class ExpectEq<utils::RGBA8>;
extern template class ExpectEq<float>;
extern template class ExpectEq<float, uint16_t>;
template <typename T>
class ExpectBetweenColors : public Expectation {
public:
// Inclusive for now
ExpectBetweenColors(T value0, T value1);
testing::AssertionResult Check(const void* data, size_t size) override;
private:
std::vector<T> mLowerColorChannels;
std::vector<T> mHigherColorChannels;
// used for printing error
std::vector<T> mValues0;
std::vector<T> mValues1;
};
// A color is considered between color0 and color1 when all channel values are within range of
// each counterparts. It doesn't matter which value is higher or lower. Essentially color =
// lerp(color0, color1, t) where t is [0,1]. But I don't want to be too strict here.
extern template class ExpectBetweenColors<utils::RGBA8>;
class CustomTextureExpectation : public Expectation {
public:
~CustomTextureExpectation() override = default;
virtual uint32_t DataSize() = 0;
};
} // namespace detail
#endif // SRC_DAWN_TESTS_DAWNTEST_H_