blob: be9f81212365f69c290555e75066e0c646349639 [file] [log] [blame]
// Copyright 2021 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.
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "dawn/dawn_proc.h"
#include "dawn/native/Adapter.h"
#include "dawn/native/DawnNative.h"
#include "dawn/native/Device.h"
#include "dawn/native/Toggles.h"
#include "dawn/native/dawn_platform.h"
#include "dawn/tests/MockCallback.h"
#include "dawn/tests/StringViewMatchers.h"
#include "dawn/utils/SystemUtils.h"
#include "dawn/utils/WGPUHelpers.h"
#include "gtest/gtest.h"
namespace dawn::native {
namespace {
using testing::_;
using testing::Contains;
using testing::EmptySizedString;
using testing::IsNull;
using testing::MockCppCallback;
using testing::NonEmptySizedString;
using testing::StrEq;
using testing::WithArg;
class DeviceCreationTest : public testing::Test {
protected:
void SetUp() override {
dawnProcSetProcs(&GetProcs());
static constexpr auto kMultipleDevicesPerAdapter =
wgpu::InstanceFeatureName::MultipleDevicesPerAdapter;
// Create an instance with default toggles and create an adapter from it.
wgpu::InstanceDescriptor safeInstanceDesc = {
.requiredFeatureCount = 1,
.requiredFeatures = &kMultipleDevicesPerAdapter,
};
instance = std::make_unique<Instance>(&safeInstanceDesc);
wgpu::RequestAdapterOptions options = {};
options.backendType = wgpu::BackendType::Null;
// Get the null adapter with default toggles.
adapter = instance->EnumerateAdapters(&options)[0];
// Create an instance with toggle AllowUnsafeAPIs enabled, and create an unsafe adapter
// from it.
const char* allowUnsafeApisToggle = "allow_unsafe_apis";
wgpu::DawnTogglesDescriptor unsafeInstanceTogglesDesc = {};
unsafeInstanceTogglesDesc.enabledToggleCount = 1;
unsafeInstanceTogglesDesc.enabledToggles = &allowUnsafeApisToggle;
wgpu::InstanceDescriptor unsafeInstanceDesc = {
.requiredFeatureCount = 1,
.requiredFeatures = &kMultipleDevicesPerAdapter,
};
unsafeInstanceDesc.nextInChain = &unsafeInstanceTogglesDesc;
unsafeInstance = std::make_unique<Instance>(&unsafeInstanceDesc);
unsafeAdapter = unsafeInstance->EnumerateAdapters(&options)[0];
ASSERT_NE(adapter.Get(), nullptr);
ASSERT_NE(unsafeAdapter.Get(), nullptr);
}
void TearDown() override {
adapter = nullptr;
unsafeAdapter = nullptr;
instance = nullptr;
unsafeInstance = nullptr;
dawnProcSetProcs(nullptr);
}
static constexpr size_t kTotalFeaturesCount = static_cast<size_t>(kEnumCount<Feature>);
std::unique_ptr<Instance> instance;
std::unique_ptr<Instance> unsafeInstance;
Adapter adapter;
Adapter unsafeAdapter;
};
// Test successful call to CreateDevice with no descriptor
TEST_F(DeviceCreationTest, CreateDeviceNoDescriptorSuccess) {
wgpu::Device device = adapter.CreateDevice();
EXPECT_NE(device, nullptr);
}
// Test successful call to CreateDevice with descriptor.
TEST_F(DeviceCreationTest, CreateDeviceSuccess) {
wgpu::DeviceDescriptor desc = {};
wgpu::Device device = adapter.CreateDevice(&desc);
EXPECT_NE(device, nullptr);
}
// Test successful call to CreateDevice with allocator descriptor.
TEST_F(DeviceCreationTest, CreateDeviceWithAllocatorSuccess) {
wgpu::DawnDeviceAllocatorControl allocationDesc = {};
allocationDesc.allocatorHeapBlockSize = 4 * 1024;
wgpu::DeviceDescriptor desc = {};
wgpu::FeatureName feature = wgpu::FeatureName::DawnDeviceAllocatorControl;
desc.requiredFeatures = &feature;
desc.requiredFeatureCount = 1;
desc.nextInChain = &allocationDesc;
wgpu::Device device = unsafeAdapter.CreateDevice(&desc);
EXPECT_NE(device, nullptr);
}
// Test failed call to CreateDevice with allocator descriptor. This is using an adapter that does
// not have DawnDeviceAllocatorControl feature enabled.
TEST_F(DeviceCreationTest, CreateDeviceWithAllocatorFailedMissingFeature) {
wgpu::DawnDeviceAllocatorControl allocationDesc = {};
allocationDesc.allocatorHeapBlockSize = 4 * 1024;
wgpu::DeviceDescriptor desc = {};
desc.nextInChain = &allocationDesc;
wgpu::Device device = adapter.CreateDevice(&desc);
EXPECT_EQ(device, nullptr);
}
// Test failed call to CreateDevice with allocator descriptor. The heap block size provided is not a
// power of two.
TEST_F(DeviceCreationTest, CreateDeviceWithAllocatorFailedHeapBlockSize) {
wgpu::DawnDeviceAllocatorControl allocationDesc = {};
allocationDesc.allocatorHeapBlockSize = 4 * 1024 + 1;
wgpu::DeviceDescriptor desc = {};
wgpu::FeatureName feature = wgpu::FeatureName::DawnDeviceAllocatorControl;
desc.requiredFeatures = &feature;
desc.requiredFeatureCount = 1;
desc.nextInChain = &allocationDesc;
wgpu::Device device = unsafeAdapter.CreateDevice(&desc);
EXPECT_EQ(device, nullptr);
}
// Test successful call to CreateDevice with toggle descriptor.
TEST_F(DeviceCreationTest, CreateDeviceWithTogglesSuccess) {
wgpu::DeviceDescriptor desc = {};
wgpu::DawnTogglesDescriptor deviceTogglesDesc = {};
desc.nextInChain = &deviceTogglesDesc;
const char* toggle = "skip_validation";
deviceTogglesDesc.enabledToggles = &toggle;
deviceTogglesDesc.enabledToggleCount = 1;
wgpu::Device device = adapter.CreateDevice(&desc);
EXPECT_NE(device, nullptr);
auto toggles = GetTogglesUsed(device.Get());
EXPECT_THAT(toggles, Contains(StrEq(toggle)));
}
// Test experimental features are guarded by DisallowUnsafeApis adapter toggle, it is inherited from
// instance but can be overriden by device toggles.
TEST_F(DeviceCreationTest, CreateDeviceRequiringExperimentalFeatures) {
// Ensure that unsafe apis are disallowed on safe adapter.
ASSERT_FALSE(FromAPI(adapter.Get())->GetTogglesState().IsEnabled(Toggle::AllowUnsafeAPIs));
// Ensure that unsafe apis are allowed unsafe adapter(s).
ASSERT_TRUE(FromAPI(unsafeAdapter.Get())->GetTogglesState().IsEnabled(Toggle::AllowUnsafeAPIs));
for (size_t i = 0; i < kTotalFeaturesCount; i++) {
Feature feature = static_cast<Feature>(i);
wgpu::FeatureName featureName = ToAPI(feature);
// Only test experimental features.
if (kFeatureNameAndInfoList[feature].featureState == FeatureInfo::FeatureState::Stable) {
continue;
}
// wgpu::FeatureName::CoreFeaturesAndLimits is required implicitly in core mode
static constexpr uint32_t kDefaultExpectedFeatureCount = 1u;
wgpu::DeviceDescriptor deviceDescriptor;
deviceDescriptor.requiredFeatures = &featureName;
deviceDescriptor.requiredFeatureCount = 1;
// Test creating device on default adapter would fail.
{
wgpu::Device device = adapter.CreateDevice(&deviceDescriptor);
EXPECT_EQ(device, nullptr);
}
// Test creating device on the adapter with AllowUnsafeApis toggle enabled would succeed.
{
deviceDescriptor.nextInChain = nullptr;
wgpu::Device device = unsafeAdapter.CreateDevice(&deviceDescriptor);
EXPECT_NE(device, nullptr);
wgpu::SupportedFeatures supportedFeatures;
device.GetFeatures(&supportedFeatures);
uint32_t expectedFeatureCount = kDefaultExpectedFeatureCount +
utils::FeatureAndImplicitlyEnabled(featureName).size();
ASSERT_EQ(expectedFeatureCount, supportedFeatures.featureCount);
bool foundFeatureName = false;
for (uint32_t fi = 0; fi < supportedFeatures.featureCount; ++fi) {
if (featureName == supportedFeatures.features[fi]) {
foundFeatureName = true;
break;
}
}
EXPECT_TRUE(foundFeatureName);
}
// Test creating device with AllowUnsafeApis enabled in device toggle descriptor will
// success on both adapter, as device toggles will override the inherited adapter toggles.
{
const char* const enableToggles[] = {"allow_unsafe_apis"};
wgpu::DawnTogglesDescriptor deviceTogglesDesc;
deviceTogglesDesc.enabledToggles = enableToggles;
deviceTogglesDesc.enabledToggleCount = 1;
deviceDescriptor.nextInChain = &deviceTogglesDesc;
// Test on adapter with AllowUnsafeApis disabled.
{
wgpu::Device device = adapter.CreateDevice(&deviceDescriptor);
EXPECT_NE(device, nullptr);
wgpu::SupportedFeatures supportedFeatures;
device.GetFeatures(&supportedFeatures);
uint32_t expectedFeatureCount =
kDefaultExpectedFeatureCount +
utils::FeatureAndImplicitlyEnabled(featureName).size();
ASSERT_EQ(expectedFeatureCount, supportedFeatures.featureCount);
bool foundFeatureName = false;
for (uint32_t fi = 0; fi < supportedFeatures.featureCount; ++fi) {
if (featureName == supportedFeatures.features[fi]) {
foundFeatureName = true;
break;
}
}
EXPECT_TRUE(foundFeatureName);
}
// Test on adapter with AllowUnsafeApis disabled.
{
wgpu::Device device = unsafeAdapter.CreateDevice(&deviceDescriptor);
EXPECT_NE(device, nullptr);
wgpu::SupportedFeatures supportedFeatures;
device.GetFeatures(&supportedFeatures);
uint32_t expectedFeatureCount =
kDefaultExpectedFeatureCount +
utils::FeatureAndImplicitlyEnabled(featureName).size();
ASSERT_EQ(expectedFeatureCount, supportedFeatures.featureCount);
bool foundFeatureName = false;
for (uint32_t fi = 0; fi < supportedFeatures.featureCount; ++fi) {
if (featureName == supportedFeatures.features[fi]) {
foundFeatureName = true;
break;
}
}
EXPECT_TRUE(foundFeatureName);
}
}
// Test creating device with AllowUnsafeApis disabled in device toggle descriptor will fail
// on both adapter, as device toggles will override the inherited adapter toggles.
{
const char* const disableToggles[] = {"allow_unsafe_apis"};
wgpu::DawnTogglesDescriptor deviceToggleDesc;
deviceToggleDesc.disabledToggles = disableToggles;
deviceToggleDesc.disabledToggleCount = 1;
deviceDescriptor.nextInChain = &deviceToggleDesc;
// Test on adapter with DisallowUnsafeApis enabled.
{
wgpu::Device device = adapter.CreateDevice(&deviceDescriptor);
EXPECT_EQ(device, nullptr);
}
// Test on adapter with DisallowUnsafeApis disabled.
{
wgpu::Device device = unsafeAdapter.CreateDevice(&deviceDescriptor);
EXPECT_EQ(device, nullptr);
}
}
}
}
TEST_F(DeviceCreationTest, CreateDeviceWithCacheSuccess) {
// Default device descriptor should have the same cache key as a device descriptor with a
// default cache descriptor.
{
wgpu::DeviceDescriptor desc = {};
wgpu::Device device1 = adapter.CreateDevice(&desc);
EXPECT_NE(device1, nullptr);
wgpu::DawnCacheDeviceDescriptor cacheDesc = {};
desc.nextInChain = &cacheDesc;
wgpu::Device device2 = adapter.CreateDevice(&desc);
EXPECT_EQ(FromAPI(device1.Get())->GetCacheKey(), FromAPI(device2.Get())->GetCacheKey());
}
// Default device descriptor should not have the same cache key as a device descriptor with
// a non-default cache descriptor.
{
wgpu::DeviceDescriptor desc = {};
wgpu::Device device1 = adapter.CreateDevice(&desc);
EXPECT_NE(device1, nullptr);
wgpu::DawnCacheDeviceDescriptor cacheDesc = {};
desc.nextInChain = &cacheDesc;
const char* isolationKey = "isolation key";
cacheDesc.isolationKey = isolationKey;
wgpu::Device device2 = adapter.CreateDevice(&desc);
EXPECT_NE(device2, nullptr);
EXPECT_NE(FromAPI(device1.Get())->GetCacheKey(), FromAPI(device2.Get())->GetCacheKey());
}
// Two non-default cache descriptors should not have the same cache key.
{
wgpu::DawnCacheDeviceDescriptor cacheDesc = {};
const char* isolationKey1 = "isolation key 1";
const char* isolationKey2 = "isolation key 2";
wgpu::DeviceDescriptor desc = {};
desc.nextInChain = &cacheDesc;
cacheDesc.isolationKey = isolationKey1;
wgpu::Device device1 = adapter.CreateDevice(&desc);
EXPECT_NE(device1, nullptr);
cacheDesc.isolationKey = isolationKey2;
wgpu::Device device2 = adapter.CreateDevice(&desc);
EXPECT_NE(device2, nullptr);
EXPECT_NE(FromAPI(device1.Get())->GetCacheKey(), FromAPI(device2.Get())->GetCacheKey());
}
}
// Test that maxImmediateSize is available iff the adapter has AllowUnsafeAPIs.
TEST_F(DeviceCreationTest, AdapterMaxImmediateSize) {
wgpu::Limits requiredLimits{.maxImmediateSize = 1};
wgpu::DeviceDescriptor deviceDesc;
deviceDesc.requiredLimits = &requiredLimits;
{
wgpu::Adapter wgpuAdapter{adapter.Get()};
wgpu::Limits limits;
wgpuAdapter.GetLimits(&limits);
EXPECT_EQ(limits.maxImmediateSize, 0u);
wgpuAdapter.RequestDevice(
&deviceDesc, wgpu::CallbackMode::AllowSpontaneous,
[](wgpu::RequestDeviceStatus status, wgpu::Device device, wgpu::StringView message) {
EXPECT_EQ(status, wgpu::RequestDeviceStatus::Error);
EXPECT_EQ(device.Get(), nullptr);
});
}
// The null backend always supports immediates.
{
wgpu::Adapter wgpuAdapter{unsafeAdapter.Get()};
wgpu::Limits limits;
wgpuAdapter.GetLimits(&limits);
EXPECT_GT(limits.maxImmediateSize, 0u);
wgpuAdapter.RequestDevice(
&deviceDesc, wgpu::CallbackMode::AllowSpontaneous,
[](wgpu::RequestDeviceStatus status, wgpu::Device device, wgpu::StringView message) {
EXPECT_EQ(status, wgpu::RequestDeviceStatus::Success);
EXPECT_NE(device.Get(), nullptr);
});
}
}
// Test failed call to CreateDevice when adapter has been consumed because instance has not the
// MultipleDevicesPerAdapter feature.
TEST_F(DeviceCreationTest, AdapterIsConsumedWithoutMultipleDevicesPerAdapterFeature) {
auto instance = std::make_unique<Instance>();
wgpu::RequestAdapterOptions options = {};
options.backendType = wgpu::BackendType::Null;
auto adapter = instance->EnumerateAdapters(&options)[0];
wgpu::Device device1 = adapter.CreateDevice();
EXPECT_NE(device1, nullptr);
wgpu::Device device2 = adapter.CreateDevice();
EXPECT_EQ(device2, nullptr);
}
// Test failed call to CreateDevice when adapter has been explicitly asked to be consumed.
TEST_F(DeviceCreationTest, AdapterIsConsumedWithConsumeAdapterDescriptor) {
wgpu::DawnConsumeAdapterDescriptor consumeAdapterDesc = {};
consumeAdapterDesc.consumeAdapter = true;
wgpu::DeviceDescriptor desc = {};
desc.nextInChain = &consumeAdapterDesc;
wgpu::Device device1 = adapter.CreateDevice(&desc);
EXPECT_NE(device1, nullptr);
desc.nextInChain = nullptr;
wgpu::Device device2 = adapter.CreateDevice(&desc);
EXPECT_EQ(device2, nullptr);
}
class DeviceCreationFutureTest : public DeviceCreationTest,
public ::testing::WithParamInterface<wgpu::CallbackMode> {
protected:
void RequestDevice(const wgpu::DeviceDescriptor* descriptor) {
wgpu::Adapter wgpuAdapter(adapter.Get());
wgpu::Future future = wgpuAdapter.RequestDevice(descriptor, GetParam(), mMockCb.Callback());
wgpu::Instance wgpuInstance(instance->Get());
switch (GetParam()) {
case wgpu::CallbackMode::WaitAnyOnly: {
// Callback should complete as soon as poll once.
EXPECT_EQ(wgpuInstance.WaitAny(future, 0), wgpu::WaitStatus::Success);
break;
}
case wgpu::CallbackMode::AllowSpontaneous:
// Callback should already be called.
break;
case wgpu::CallbackMode::AllowProcessEvents:
wgpuInstance.ProcessEvents();
break;
}
}
MockCppCallback<wgpu::RequestDeviceCallback<void>*> mMockCb;
};
INSTANTIATE_TEST_SUITE_P(,
DeviceCreationFutureTest,
::testing::ValuesIn(std::initializer_list<wgpu::CallbackMode>{
wgpu::CallbackMode::WaitAnyOnly,
wgpu::CallbackMode::AllowProcessEvents,
wgpu::CallbackMode::AllowSpontaneous}));
// Test successful call to RequestDevice with descriptor
TEST_P(DeviceCreationFutureTest, RequestDeviceSuccess) {
wgpu::Device device;
EXPECT_CALL(mMockCb, Call(wgpu::RequestDeviceStatus::Success, _, EmptySizedString()))
.WillOnce(WithArg<1>([&](wgpu::Device result) { device = std::move(result); }));
wgpu::DeviceDescriptor desc = {};
RequestDevice(&desc);
EXPECT_NE(device, nullptr);
}
// Test successful call to RequestDevice with a null descriptor
TEST_P(DeviceCreationFutureTest, RequestDeviceNullDescriptorSuccess) {
wgpu::Device device;
EXPECT_CALL(mMockCb, Call(wgpu::RequestDeviceStatus::Success, _, EmptySizedString()))
.WillOnce(WithArg<1>([&](wgpu::Device result) { device = std::move(result); }));
RequestDevice(nullptr);
EXPECT_NE(device, nullptr);
}
// Test failing call to RequestDevice with invalid feature
TEST_P(DeviceCreationFutureTest, RequestDeviceFailure) {
wgpu::Device device;
EXPECT_CALL(mMockCb, Call(wgpu::RequestDeviceStatus::Error, IsNull(), NonEmptySizedString()))
.WillOnce(WithArg<1>([&](wgpu::Device result) { device = std::move(result); }));
wgpu::DeviceDescriptor desc = {};
wgpu::FeatureName invalidFeature = static_cast<wgpu::FeatureName>(WGPUFeatureName_Force32);
desc.requiredFeatures = &invalidFeature;
desc.requiredFeatureCount = 1;
RequestDevice(&desc);
}
} // anonymous namespace
} // namespace dawn::native