| // Copyright 2017 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #ifdef UNSAFE_BUFFERS_BUILD | |
| // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. | |
| #pragma allow_unsafe_buffers | |
| #endif | |
| #include "src/dawn/tests/unittests/validation/ValidationTest.h" | |
| #include <webgpu/webgpu.h> | |
| #include <algorithm> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include "dawn/dawn_proc.h" | |
| #include "dawn/native/NullBackend.h" | |
| #include "dawn/webgpu_cpp_print.h" | |
| #include "src/dawn/common/SystemUtils.h" | |
| #include "src/dawn/native/Adapter.h" | |
| #include "src/dawn/tests/PartitionAllocSupport.h" | |
| #include "src/dawn/tests/StringViewMatchers.h" | |
| #include "src/dawn/tests/ToggleParser.h" | |
| #include "src/dawn/utils/WireHelper.h" | |
| #include "src/utils/assert.h" | |
| #include "src/utils/crash_handler.h" | |
| namespace { | |
| bool gUseWire = false; | |
| // NOLINTNEXTLINE(runtime/string) | |
| std::string gWireTraceDir = ""; | |
| std::unique_ptr<ToggleParser> gToggleParser = nullptr; | |
| static ValidationTest* gCurrentTest = nullptr; | |
| } // namespace | |
| void InitDawnValidationTestEnvironment(int argc, char** argv) { | |
| dawn::InstallCrashHandler(argv[0]); | |
| dawn::InitializePartitionAllocForTesting(); | |
| dawn::InitializeDanglingPointerDetectorForTesting(); | |
| gToggleParser = std::make_unique<ToggleParser>(); | |
| for (int i = 1; i < argc; ++i) { | |
| if (strcmp("-w", argv[i]) == 0 || strcmp("--use-wire", argv[i]) == 0) { | |
| gUseWire = true; | |
| continue; | |
| } | |
| constexpr const char kWireTraceDirArg[] = "--wire-trace-dir="; | |
| size_t argLen = sizeof(kWireTraceDirArg) - 1; | |
| if (strncmp(argv[i], kWireTraceDirArg, argLen) == 0) { | |
| gWireTraceDir = argv[i] + argLen; | |
| continue; | |
| } | |
| if (gToggleParser->ParseEnabledToggles(argv[i])) { | |
| continue; | |
| } | |
| if (gToggleParser->ParseDisabledToggles(argv[i])) { | |
| continue; | |
| } | |
| if (strcmp("-h", argv[i]) == 0 || strcmp("--help", argv[i]) == 0) { | |
| dawn::InfoLog() | |
| << "\n\nUsage: " << argv[0] | |
| << " [GTEST_FLAGS...] [-w]\n" | |
| " [--enable-toggles=toggles] [--disable-toggles=toggles]\n" | |
| " -w, --use-wire: Run the tests through the wire (defaults to no wire)\n" | |
| " --enable-toggles: Comma-delimited list of Dawn toggles to enable.\n" | |
| " ex.) skip_validation,disable_robustness,turn_off_vsync\n" | |
| " --disable-toggles: Comma-delimited list of Dawn toggles to disable\n"; | |
| continue; | |
| } | |
| // Skip over args that look like they're for Googletest. | |
| constexpr const char kGtestArgPrefix[] = "--gtest_"; | |
| if (strncmp(kGtestArgPrefix, argv[i], sizeof(kGtestArgPrefix) - 1) == 0) { | |
| continue; | |
| } | |
| dawn::WarningLog() << " Unused argument: " << argv[i]; | |
| } | |
| } | |
| ValidationTest::ValidationTest() { | |
| gCurrentTest = this; | |
| DawnProcTable procs = dawn::native::GetProcs(); | |
| // Forward to dawn::native instanceRequestAdapter, but save the returned adapter in | |
| // gCurrentTest->mBackendAdapter. | |
| procs.instanceRequestAdapter = [](WGPUInstance self, const WGPURequestAdapterOptions* options, | |
| WGPURequestAdapterCallbackInfo callbackInfo) -> WGPUFuture { | |
| DAWN_ASSERT(gCurrentTest); | |
| DAWN_ASSERT(callbackInfo.mode == WGPUCallbackMode_AllowSpontaneous); | |
| return dawn::native::GetProcs().instanceRequestAdapter( | |
| self, options, | |
| {nullptr, WGPUCallbackMode_AllowSpontaneous, | |
| [](WGPURequestAdapterStatus status, WGPUAdapter cAdapter, WGPUStringView message, | |
| void* userdata, void*) { | |
| gCurrentTest->mBackendAdapter = dawn::native::FromAPI(cAdapter); | |
| auto* info = static_cast<WGPURequestAdapterCallbackInfo*>(userdata); | |
| info->callback(status, cAdapter, message, info->userdata1, info->userdata2); | |
| delete info; | |
| }, | |
| new WGPURequestAdapterCallbackInfo(callbackInfo), nullptr}); | |
| }; | |
| // Forward to dawn::native adapterRequestDevice, but save the returned backend device in | |
| // gCurrentTest->mLastCreatedBackendDevice. | |
| procs.adapterRequestDevice = [](WGPUAdapter self, const WGPUDeviceDescriptor* descriptor, | |
| WGPURequestDeviceCallbackInfo callbackInfo) -> WGPUFuture { | |
| DAWN_ASSERT(gCurrentTest); | |
| DAWN_ASSERT(callbackInfo.mode == WGPUCallbackMode_AllowSpontaneous); | |
| wgpu::DeviceDescriptor deviceDesc = {}; | |
| if (descriptor != nullptr) { | |
| deviceDesc = *(reinterpret_cast<const wgpu::DeviceDescriptor*>(descriptor)); | |
| } | |
| // Set the toggles for the device. We start with all test specific toggles, then toggle | |
| // flags so that toggle flags will always take precedence. Note that disabling toggles also | |
| // take precedence. | |
| wgpu::DawnTogglesDescriptor deviceTogglesDesc; | |
| deviceTogglesDesc.nextInChain = deviceDesc.nextInChain; | |
| deviceDesc.nextInChain = &deviceTogglesDesc; | |
| auto enabledToggles = gCurrentTest->GetEnabledToggles(); | |
| auto disabledToggles = gCurrentTest->GetDisabledToggles(); | |
| for (const std::string& toggle : gToggleParser->GetEnabledToggles()) { | |
| enabledToggles.push_back(toggle.c_str()); | |
| } | |
| for (const std::string& toggle : gToggleParser->GetDisabledToggles()) { | |
| disabledToggles.push_back(toggle.c_str()); | |
| } | |
| deviceTogglesDesc.enabledToggles = enabledToggles.data(); | |
| deviceTogglesDesc.enabledToggleCount = enabledToggles.size(); | |
| deviceTogglesDesc.disabledToggles = disabledToggles.data(); | |
| deviceTogglesDesc.disabledToggleCount = disabledToggles.size(); | |
| return dawn::native::GetProcs().adapterRequestDevice( | |
| self, reinterpret_cast<WGPUDeviceDescriptor*>(&deviceDesc), | |
| {nullptr, WGPUCallbackMode_AllowSpontaneous, | |
| [](WGPURequestDeviceStatus status, WGPUDevice cDevice, WGPUStringView message, | |
| void* userdata, void*) { | |
| gCurrentTest->mLastCreatedBackendDevice = cDevice; | |
| auto* info = static_cast<WGPURequestDeviceCallbackInfo*>(userdata); | |
| info->callback(status, cDevice, message, info->userdata1, info->userdata2); | |
| delete info; | |
| }, | |
| new WGPURequestDeviceCallbackInfo(callbackInfo), nullptr}); | |
| }; | |
| mWireHelper = dawn::utils::CreateWireHelper(procs, gUseWire, gWireTraceDir.c_str()); | |
| } | |
| void ValidationTest::SetUp() { | |
| // By default create the instance with toggle AllowUnsafeAPIs enabled, which would be inherited | |
| // to adapter and device toggles and allow us to test unsafe apis (including experimental | |
| // features). To test device with AllowUnsafeAPIs disabled, require it in device toggles | |
| // descriptor to override the inheritance. Alternatively, override AllowUnsafeAPIs() if | |
| // querying for features via the adapter, i.e. prior to device creation. | |
| wgpu::DawnTogglesDescriptor instanceToggles = {}; | |
| if (AllowUnsafeAPIs()) { | |
| static const char* allowUnsafeApisToggle = "allow_unsafe_apis"; | |
| instanceToggles.enabledToggleCount = 1; | |
| instanceToggles.enabledToggles = &allowUnsafeApisToggle; | |
| } | |
| std::vector<const char*> blocklistedFeatures = GetWGSLBlocklistedFeatures(); | |
| dawn::native::DawnWGSLBlocklist blockList; | |
| blockList.blocklistedFeatures = blocklistedFeatures; | |
| blockList.nextInChain = &instanceToggles; | |
| wgpu::InstanceDescriptor instanceDesc = {}; | |
| instanceDesc.nextInChain = &blockList; | |
| static constexpr auto kRequiredFeatures = | |
| std::array{wgpu::InstanceFeatureName::MultipleDevicesPerAdapter, | |
| wgpu::InstanceFeatureName::ShaderSourceSPIRV}; | |
| instanceDesc.requiredFeatureCount = kRequiredFeatures.size(); | |
| instanceDesc.requiredFeatures = kRequiredFeatures.data(); | |
| SetUp(&instanceDesc); | |
| } | |
| ValidationTest::~ValidationTest() { | |
| // We need to destroy Dawn objects before the wire helper which sets procs to null otherwise the | |
| // dawn*Release will call a nullptr | |
| device = nullptr; | |
| adapter = nullptr; | |
| instance = nullptr; | |
| mWireHelper.reset(); | |
| // Check that all devices were destructed. | |
| // Note that if the test is skipped before SetUp is called, mDawnInstance will not get set and | |
| // remain nullptr. | |
| if (mDawnInstance) { | |
| EXPECT_EQ(mDawnInstance->GetDeviceCountForTesting(), 0u); | |
| } | |
| gCurrentTest = nullptr; | |
| } | |
| void ValidationTest::TearDown() { | |
| FlushWire(); | |
| ASSERT_FALSE(mExpectError); | |
| // Note that if the test is skipped before SetUp is called, mDawnInstance will not get set and | |
| // remain nullptr. | |
| if (mDawnInstance) { | |
| EXPECT_EQ(mLastWarningCount, mDawnInstance->GetDeprecationWarningCountForTesting()); | |
| } | |
| // The device will be destroyed soon after, so we want to set the expectation. | |
| ExpectDeviceDestruction(); | |
| } | |
| void ValidationTest::StartExpectDeviceError(testing::Matcher<std::string> errorMatcher) { | |
| mExpectError = true; | |
| mError = false; | |
| mErrorMatcher = errorMatcher; | |
| } | |
| void ValidationTest::StartExpectDeviceError() { | |
| StartExpectDeviceError(testing::_); | |
| } | |
| bool ValidationTest::EndExpectDeviceError() { | |
| mExpectError = false; | |
| mErrorMatcher = testing::_; | |
| return mError; | |
| } | |
| std::string ValidationTest::GetLastDeviceErrorMessage() const { | |
| return mDeviceErrorMessage; | |
| } | |
| void ValidationTest::StartExpectDeviceLog(wgpu::LoggingType type, | |
| testing::Matcher<std::string> message) { | |
| mExpectLog = std::tuple(type, message); | |
| mGotLog = false; | |
| } | |
| bool ValidationTest::EndExpectDeviceLog() { | |
| mExpectLog.reset(); | |
| return mGotLog; | |
| } | |
| void ValidationTest::StartExpectNoDeviceLog() { | |
| mExpectLog = std::tuple(kExpectNoLog, testing::_); | |
| } | |
| void ValidationTest::EndExpectNoDeviceLog() { | |
| mExpectLog.reset(); | |
| } | |
| void ValidationTest::ExpectDeviceDestruction() { | |
| mExpectDestruction = true; | |
| } | |
| bool ValidationTest::UsesWire() const { | |
| return gUseWire; | |
| } | |
| void ValidationTest::FlushWire() { | |
| EXPECT_TRUE(mWireHelper->FlushClient()); | |
| } | |
| void ValidationTest::WaitForAllOperations() { | |
| mWireHelper->WaitUntilIdle(mDawnInstance.get(), instance); | |
| } | |
| const dawn::native::ToggleInfo* ValidationTest::GetToggleInfo(const char* name) const { | |
| return mDawnInstance->GetToggleInfo(name); | |
| } | |
| bool ValidationTest::HasToggleEnabled(const char* toggle) const { | |
| auto toggles = dawn::native::GetTogglesUsed(backendDevice); | |
| return std::find_if(toggles.begin(), toggles.end(), [toggle](const char* name) { | |
| return strcmp(toggle, name) == 0; | |
| }) != toggles.end(); | |
| } | |
| const dawn::utils::ComboLimits& ValidationTest::GetSupportedLimits() const { | |
| return deviceLimits; | |
| } | |
| bool ValidationTest::AllowUnsafeAPIs() { | |
| return true; | |
| } | |
| std::vector<wgpu::FeatureName> ValidationTest::GetRequiredFeatures() { | |
| return {}; | |
| } | |
| void ValidationTest::GetRequiredLimits(const dawn::utils::ComboLimits& supported, | |
| dawn::utils::ComboLimits& required) {} | |
| std::vector<const char*> ValidationTest::GetEnabledToggles() { | |
| return {}; | |
| } | |
| std::vector<const char*> ValidationTest::GetDisabledToggles() { | |
| return {}; | |
| } | |
| std::vector<const char*> ValidationTest::GetWGSLBlocklistedFeatures() { | |
| return {}; | |
| } | |
| dawn::utils::WireHelper* ValidationTest::GetWireHelper() const { | |
| return mWireHelper.get(); | |
| } | |
| uint64_t ValidationTest::GetInstanceDeprecationCountForTesting() { | |
| return mDawnInstance->GetDeprecationWarningCountForTesting(); | |
| } | |
| wgpu::Device ValidationTest::RequestDeviceSync(const wgpu::DeviceDescriptor& deviceDesc) { | |
| DAWN_ASSERT(adapter); | |
| wgpu::Device apiDevice; | |
| adapter.RequestDevice(&deviceDesc, wgpu::CallbackMode::AllowSpontaneous, | |
| [&apiDevice](wgpu::RequestDeviceStatus status, wgpu::Device result, | |
| wgpu::StringView message) { | |
| if (status != wgpu::RequestDeviceStatus::Success) { | |
| ADD_FAILURE() << "Unable to create device: " << message; | |
| DAWN_ASSERT(false); | |
| } | |
| apiDevice = std::move(result); | |
| }); | |
| FlushWire(); | |
| DAWN_ASSERT(apiDevice); | |
| return apiDevice; | |
| } | |
| dawn::native::Adapter& ValidationTest::GetBackendAdapter() { | |
| return mBackendAdapter; | |
| } | |
| void ValidationTest::SetUp(const wgpu::InstanceDescriptor* nativeDesc, | |
| const wgpu::InstanceDescriptor* wireDesc) { | |
| std::string traceName = | |
| std::string(::testing::UnitTest::GetInstance()->current_test_info()->test_suite_name()) + | |
| "_" + ::testing::UnitTest::GetInstance()->current_test_info()->name(); | |
| mWireHelper->BeginWireTrace(traceName.c_str()); | |
| // Initialize the instances. | |
| std::tie(instance, mDawnInstance) = mWireHelper->CreateInstances(nativeDesc, wireDesc); | |
| // Initialize the adapter. | |
| wgpu::RequestAdapterOptions options = {}; | |
| options.backendType = wgpu::BackendType::Null; | |
| options.featureLevel = gCurrentTest->UseCompatibilityMode() ? wgpu::FeatureLevel::Compatibility | |
| : wgpu::FeatureLevel::Core; | |
| instance.RequestAdapter(&options, wgpu::CallbackMode::AllowSpontaneous, | |
| [this](wgpu::RequestAdapterStatus, wgpu::Adapter result, | |
| wgpu::StringView) -> void { adapter = std::move(result); }); | |
| FlushWire(); | |
| DAWN_ASSERT(adapter); | |
| // Initialize the device. | |
| wgpu::DeviceDescriptor deviceDescriptor = {}; | |
| deviceDescriptor.SetDeviceLostCallback( | |
| wgpu::CallbackMode::AllowSpontaneous, | |
| [this](const wgpu::Device&, wgpu::DeviceLostReason reason, wgpu::StringView message) { | |
| if (mExpectDestruction) { | |
| EXPECT_EQ(reason, wgpu::DeviceLostReason::Destroyed); | |
| return; | |
| } | |
| ADD_FAILURE() << "Device lost during test: " << message; | |
| DAWN_ASSERT(false); | |
| }); | |
| deviceDescriptor.SetUncapturedErrorCallback( | |
| [](const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView message, | |
| ValidationTest* self) { | |
| DAWN_ASSERT(type != wgpu::ErrorType::NoError); | |
| ASSERT_TRUE(self->mExpectError) << "Got unexpected device error: " << message; | |
| ASSERT_FALSE(self->mError) << "Got two errors in expect block, first one is:\n" // | |
| << self->mDeviceErrorMessage // | |
| << "\nsecond one is:\n" // | |
| << message; | |
| self->mDeviceErrorMessage = message; | |
| if (self->mExpectError) { | |
| ASSERT_THAT(message, testing::SizedStringMatches(self->mErrorMatcher)); | |
| } | |
| self->mError = true; | |
| }, | |
| this); | |
| // Set the required features for the device. | |
| auto requiredFeatures = GetRequiredFeatures(); | |
| deviceDescriptor.requiredFeatures = requiredFeatures.data(); | |
| deviceDescriptor.requiredFeatureCount = requiredFeatures.size(); | |
| dawn::utils::ComboLimits supportedLimits; | |
| dawn::native::GetProcs().adapterGetLimits( | |
| mBackendAdapter.Get(), reinterpret_cast<WGPULimits*>(supportedLimits.GetLinked())); | |
| dawn::utils::ComboLimits requiredLimits{}; | |
| GetRequiredLimits(supportedLimits, requiredLimits); | |
| deviceDescriptor.requiredLimits = requiredLimits.GetLinked(); | |
| device = RequestDeviceSync(deviceDescriptor); | |
| DAWN_ASSERT(device); | |
| device.SetLoggingCallback( | |
| [](wgpu::LoggingType type, wgpu::StringView message, ValidationTest* self) { | |
| // Note we ignore all logs that happen outside of ASSERT_(NO_)DEVICE_LOG. | |
| if (self->mExpectLog) { | |
| const auto& [expectedType, expectedMessage] = self->mExpectLog.value(); | |
| if (expectedType == kExpectNoLog) { | |
| FAIL() << "Unexpected log during ASSERT_NO_DEVICE_LOG:\n" << message; | |
| } else { | |
| ASSERT_EQ(type, expectedType); | |
| ASSERT_THAT(message, testing::SizedStringMatches(expectedMessage)); | |
| self->mGotLog = true; | |
| } | |
| } | |
| }, | |
| this); | |
| device.GetLimits(deviceLimits.GetLinked()); | |
| // We only want to set the backendDevice when the device was created via the test setup. | |
| backendDevice = mLastCreatedBackendDevice; | |
| } | |
| bool ValidationTest::UseCompatibilityMode() const { | |
| return false; | |
| } | |
| ValidationTest::PlaceholderRenderPass::PlaceholderRenderPass(const wgpu::Device& device) | |
| : attachmentFormat(wgpu::TextureFormat::RGBA8Unorm), width(400), height(400) { | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.size.width = width; | |
| descriptor.size.height = height; | |
| descriptor.size.depthOrArrayLayers = 1; | |
| descriptor.sampleCount = 1; | |
| descriptor.format = attachmentFormat; | |
| descriptor.mipLevelCount = 1; | |
| descriptor.usage = wgpu::TextureUsage::RenderAttachment; | |
| attachment = device.CreateTexture(&descriptor); | |
| wgpu::TextureView view = attachment.CreateView(); | |
| mColorAttachment.view = view; | |
| mColorAttachment.resolveTarget = nullptr; | |
| mColorAttachment.clearValue = {0.0f, 0.0f, 0.0f, 0.0f}; | |
| mColorAttachment.loadOp = wgpu::LoadOp::Clear; | |
| mColorAttachment.storeOp = wgpu::StoreOp::Store; | |
| colorAttachmentCount = 1; | |
| colorAttachments = &mColorAttachment; | |
| depthStencilAttachment = nullptr; | |
| } |