877 lines
41 KiB
C++
877 lines
41 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 <atomic>
|
|
#include <memory>
|
|
#include <queue>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#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"
|
|
#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 dawn::utils {
|
|
class PlatformDebugLogger;
|
|
class TerribleCommandBuffer;
|
|
class WireHelper;
|
|
} // namespace dawn::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;
|
|
bool IsImplicitDeviceSyncEnabled() 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> CreateInstanceAndDiscoverPhysicalDevices(
|
|
dawn::platform::Platform* platform = nullptr);
|
|
std::unique_ptr<dawn::native::Instance> mInstance;
|
|
|
|
private:
|
|
void ParseArgs(int argc, char** argv);
|
|
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;
|
|
bool mEnableImplicitDeviceSync = 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<dawn::utils::PlatformDebugLogger> mPlatformDebugLogger;
|
|
};
|
|
|
|
class DawnTestBase {
|
|
friend class DawnPerfTestBase;
|
|
|
|
public:
|
|
explicit DawnTestBase(const AdapterTestParam& param);
|
|
virtual ~DawnTestBase();
|
|
|
|
void SetUp();
|
|
void TearDown();
|
|
|
|
bool IsD3D11() const;
|
|
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 IsImplicitDeviceSyncEnabled() const;
|
|
bool IsBackendValidationEnabled() const;
|
|
bool IsFullBackendValidationEnabled() const;
|
|
bool IsCompatibilityMode() const;
|
|
bool RunSuppressedTests() const;
|
|
|
|
bool IsDXC() const;
|
|
|
|
bool IsAsan() const;
|
|
bool IsTsan() 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,
|
|
const wgpu::AdapterProperties& properties,
|
|
size_t index) const;
|
|
|
|
template <class ParamType>
|
|
std::string operator()(const ::testing::TestParamInfo<ParamType>& info) const {
|
|
return SanitizeParamName(::testing::PrintToStringParamName()(info),
|
|
info.param.adapterProperties, 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 = dawn::utils::GetTexelBlockSizeInBytes(format);
|
|
uint32_t texelComponentCount =
|
|
dawn::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::Instance = 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();
|
|
|
|
void* GetUniqueUserdata();
|
|
|
|
private:
|
|
dawn::utils::ScopedAutoreleasePool mObjCAutoreleasePool;
|
|
AdapterTestParam mParam;
|
|
std::unique_ptr<dawn::utils::WireHelper> mWireHelper;
|
|
wgpu::Instance mInstance;
|
|
wgpu::Adapter mAdapter;
|
|
|
|
// Helps generate unique userdata values passed to deviceLostUserdata.
|
|
std::atomic<uintptr_t> mNextUniqueUserdata = 0;
|
|
|
|
// 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, const WGPUDeviceDescriptor* descriptor);
|
|
|
|
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);
|
|
std::atomic<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;
|
|
|
|
dawn::Mutex mMutex;
|
|
};
|
|
|
|
#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<dawn::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<dawn::utils::RGBA8>;
|
|
|
|
class CustomTextureExpectation : public Expectation {
|
|
public:
|
|
~CustomTextureExpectation() override = default;
|
|
virtual uint32_t DataSize() = 0;
|
|
};
|
|
|
|
} // namespace detail
|
|
|
|
#endif // SRC_DAWN_TESTS_DAWNTEST_H_
|