blob: 12dff71a028370b9f052262a99c6240801912372 [file] [log] [blame]
// Copyright 2023 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 <webgpu/webgpu_cpp.h>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "dawn/common/GPUInfo.h"
#include "dawn/common/StringViewUtils.h"
#include "dawn/dawn_proc.h"
#include "dawn/native/DawnNative.h"
#include "dawn/tests/DawnTest.h"
#include "dawn/tests/MockCallback.h"
#include "dawn/tests/StringViewMatchers.h"
#include "gtest/gtest.h"
namespace dawn {
namespace {
using testing::_;
using testing::EmptySizedString;
using testing::MockCallback;
using testing::SaveArg;
class AdapterCreationTest : public ::testing::TestWithParam<std::optional<wgpu::CallbackMode>> {
protected:
void SetUp() override {
// TODO(345685638): these tests are timed out on TSAN bots.
DAWN_TEST_UNSUPPORTED_IF(DawnTest::IsTsan());
dawnProcSetProcs(&native::GetProcs());
{
auto nativeInstance = std::make_unique<native::Instance>();
for (native::Adapter& nativeAdapter : nativeInstance->EnumerateAdapters()) {
anyAdapterAvailable = true;
wgpu::AdapterInfo info;
nativeAdapter.GetInfo(&info);
if (info.compatibilityMode) {
continue;
}
swiftShaderAvailable |= gpu_info::IsGoogleSwiftshader(info.vendorID, info.deviceID);
discreteGPUAvailable |= info.adapterType == wgpu::AdapterType::DiscreteGPU;
integratedGPUAvailable |= info.adapterType == wgpu::AdapterType::IntegratedGPU;
}
}
instance = wgpu::CreateInstance();
}
void TearDown() override {
instance = nullptr;
dawnProcSetProcs(nullptr);
}
void RequestAdapter(const wgpu::Instance& inst,
const wgpu::RequestAdapterOptions* options,
WGPURequestAdapterCallback callback,
void* userdata) {
if (GetParam() == std::nullopt) {
// Legacy RequestAdapter. It should call the callback immediately.
inst.RequestAdapter(options, callback, userdata);
return;
}
wgpu::Future future =
inst.RequestAdapter(options, {nullptr, *GetParam(), callback, userdata});
switch (*GetParam()) {
case wgpu::CallbackMode::WaitAnyOnly: {
// Callback should complete as soon as poll once.
wgpu::FutureWaitInfo waitInfo = {future};
EXPECT_EQ(inst.WaitAny(1, &waitInfo, 0), wgpu::WaitStatus::Success);
ASSERT_TRUE(waitInfo.completed);
break;
}
case wgpu::CallbackMode::AllowSpontaneous:
// Callback should already be called.
break;
case wgpu::CallbackMode::AllowProcessEvents:
inst.ProcessEvents();
break;
}
}
wgpu::Instance instance;
bool anyAdapterAvailable = false;
bool swiftShaderAvailable = false;
bool discreteGPUAvailable = false;
bool integratedGPUAvailable = false;
};
INSTANTIATE_TEST_SUITE_P(
,
AdapterCreationTest,
::testing::ValuesIn(std::initializer_list<std::optional<wgpu::CallbackMode>>{
wgpu::CallbackMode::WaitAnyOnly, wgpu::CallbackMode::AllowProcessEvents,
wgpu::CallbackMode::AllowSpontaneous, std::nullopt}));
// Test that requesting the default adapter works
TEST_P(AdapterCreationTest, DefaultAdapter) {
wgpu::RequestAdapterOptions options = {};
MockCallback<WGPURequestAdapterCallback> cb;
WGPUAdapter cAdapter = nullptr;
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this))
.WillOnce(SaveArg<1>(&cAdapter));
RequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
}
// Test that passing nullptr for the options gets the default adapter
TEST_P(AdapterCreationTest, NullGivesDefaultAdapter) {
wgpu::RequestAdapterOptions options = {};
MockCallback<WGPURequestAdapterCallback> cb;
WGPUAdapter cAdapter = nullptr;
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this))
.WillOnce(SaveArg<1>(&cAdapter));
RequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this + 1))
.WillOnce(SaveArg<1>(&cAdapter));
RequestAdapter(instance, nullptr, cb.Callback(), cb.MakeUserdata(this + 1));
wgpu::Adapter adapter2 = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter2 != nullptr, anyAdapterAvailable);
}
// Test that requesting the fallback adapter returns SwiftShader.
TEST_P(AdapterCreationTest, FallbackAdapter) {
wgpu::RequestAdapterOptions options = {};
options.forceFallbackAdapter = true;
MockCallback<WGPURequestAdapterCallback> cb;
WGPUAdapter cAdapter = nullptr;
if (swiftShaderAvailable) {
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this))
.WillOnce(SaveArg<1>(&cAdapter));
} else {
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Unavailable, nullptr, _, this))
.WillOnce(SaveArg<1>(&cAdapter));
}
RequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter != nullptr, swiftShaderAvailable);
if (adapter != nullptr) {
wgpu::AdapterInfo info;
adapter.GetInfo(&info);
EXPECT_EQ(info.adapterType, wgpu::AdapterType::CPU);
EXPECT_TRUE(gpu_info::IsGoogleSwiftshader(info.vendorID, info.deviceID));
}
}
// Test that requesting a high performance GPU works
TEST_P(AdapterCreationTest, PreferHighPerformance) {
wgpu::RequestAdapterOptions options = {};
options.powerPreference = wgpu::PowerPreference::HighPerformance;
MockCallback<WGPURequestAdapterCallback> cb;
WGPUAdapter cAdapter = nullptr;
if (anyAdapterAvailable) {
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this))
.WillOnce(SaveArg<1>(&cAdapter));
} else {
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Unavailable, nullptr, _, this))
.WillOnce(SaveArg<1>(&cAdapter));
}
RequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
if (discreteGPUAvailable) {
wgpu::AdapterInfo info;
wgpu::DawnAdapterPropertiesPowerPreference powerPreferenceProperties;
info.nextInChain = &powerPreferenceProperties;
adapter.GetInfo(&info);
EXPECT_EQ(info.adapterType, wgpu::AdapterType::DiscreteGPU);
EXPECT_EQ(powerPreferenceProperties.powerPreference, options.powerPreference);
}
}
// Test that requesting a low power GPU works
TEST_P(AdapterCreationTest, PreferLowPower) {
wgpu::RequestAdapterOptions options = {};
options.powerPreference = wgpu::PowerPreference::LowPower;
MockCallback<WGPURequestAdapterCallback> cb;
WGPUAdapter cAdapter = nullptr;
if (anyAdapterAvailable) {
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this))
.WillOnce(SaveArg<1>(&cAdapter));
} else {
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Unavailable, nullptr, _, this))
.WillOnce(SaveArg<1>(&cAdapter));
}
RequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
if (integratedGPUAvailable) {
wgpu::AdapterInfo info;
wgpu::DawnAdapterPropertiesPowerPreference powerPreferenceProperties;
info.nextInChain = &powerPreferenceProperties;
adapter.GetInfo(&info);
EXPECT_EQ(info.adapterType, wgpu::AdapterType::IntegratedGPU);
EXPECT_EQ(powerPreferenceProperties.powerPreference, options.powerPreference);
}
}
// Test that requesting a Compatibility adapter is supported.
TEST_P(AdapterCreationTest, Compatibility) {
wgpu::RequestAdapterOptions options = {};
options.compatibilityMode = true;
MockCallback<WGPURequestAdapterCallback> cb;
WGPUAdapter cAdapter = nullptr;
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this))
.WillOnce(SaveArg<1>(&cAdapter));
RequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
wgpu::AdapterInfo info;
adapter.GetInfo(&info);
EXPECT_TRUE(info.compatibilityMode);
}
// Test that requesting a Non-Compatibility adapter is supported and is default.
TEST_P(AdapterCreationTest, NonCompatibility) {
wgpu::RequestAdapterOptions options = {};
MockCallback<WGPURequestAdapterCallback> cb;
WGPUAdapter cAdapter = nullptr;
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this))
.WillOnce(SaveArg<1>(&cAdapter));
RequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
wgpu::AdapterInfo info;
adapter.GetInfo(&info);
EXPECT_FALSE(info.compatibilityMode);
}
// Test that GetInstance() returns the correct Instance.
TEST_P(AdapterCreationTest, GetInstance) {
wgpu::RequestAdapterOptions options = {};
MockCallback<WGPURequestAdapterCallback> cb;
WGPUAdapter cAdapter = nullptr;
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this))
.WillOnce(SaveArg<1>(&cAdapter));
RequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
EXPECT_EQ(adapter.GetInstance().Get(), instance.Get());
}
// Test that calling AdapterGetInfo returns separate allocations for strings.
// However, the string contents are equivalent.
TEST_P(AdapterCreationTest, InfoUnique) {
wgpu::RequestAdapterOptions options = {};
MockCallback<WGPURequestAdapterCallback> cb;
WGPUAdapter cAdapter = nullptr;
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this))
.WillOnce(SaveArg<1>(&cAdapter));
RequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
if (!adapter) {
return;
}
wgpu::AdapterInfo info1;
wgpu::AdapterInfo info2;
adapter.GetInfo(&info1);
adapter.GetInfo(&info2);
EXPECT_NE(info1.vendor.data, info2.vendor.data);
EXPECT_EQ(info1.vendor, info2.vendor);
EXPECT_NE(info1.architecture.data, info2.architecture.data);
EXPECT_EQ(info1.architecture, info2.architecture);
EXPECT_NE(info1.device.data, info2.device.data);
EXPECT_EQ(info1.device, info2.device);
EXPECT_NE(info1.description.data, info2.description.data);
EXPECT_EQ(info1.description, info2.description);
}
// Test move assignment of the adapter info.
TEST_P(AdapterCreationTest, InfoMoveAssign) {
wgpu::RequestAdapterOptions options = {};
MockCallback<WGPURequestAdapterCallback> cb;
WGPUAdapter cAdapter = nullptr;
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this))
.WillOnce(SaveArg<1>(&cAdapter));
RequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
if (!adapter) {
return;
}
wgpu::AdapterInfo info1;
wgpu::AdapterInfo info2;
adapter.GetInfo(&info1);
adapter.GetInfo(&info2);
wgpu::StringView vendor = info1.vendor;
wgpu::StringView architecture = info1.architecture;
wgpu::StringView device = info1.device;
wgpu::StringView description = info1.description;
wgpu::BackendType backendType = info1.backendType;
wgpu::AdapterType adapterType = info1.adapterType;
uint32_t vendorID = info1.vendorID;
uint32_t deviceID = info1.deviceID;
bool compatibilityMode = info1.compatibilityMode;
info2 = std::move(info1);
// Expect info2 to have info1's old contents.
EXPECT_EQ(info2.vendor, vendor);
EXPECT_EQ(info2.architecture, architecture);
EXPECT_EQ(info2.device, device);
EXPECT_EQ(info2.description, description);
EXPECT_EQ(info2.backendType, backendType);
EXPECT_EQ(info2.adapterType, adapterType);
EXPECT_EQ(info2.vendorID, vendorID);
EXPECT_EQ(info2.deviceID, deviceID);
EXPECT_EQ(info2.compatibilityMode, compatibilityMode);
// Expect info1 to be empty.
EXPECT_EQ(info1.vendor.data, nullptr);
EXPECT_EQ(info1.vendor.length, wgpu::kStrlen);
EXPECT_EQ(info1.architecture.data, nullptr);
EXPECT_EQ(info1.architecture.length, wgpu::kStrlen);
EXPECT_EQ(info1.device.data, nullptr);
EXPECT_EQ(info1.device.length, wgpu::kStrlen);
EXPECT_EQ(info1.description.data, nullptr);
EXPECT_EQ(info1.description.length, wgpu::kStrlen);
EXPECT_EQ(info1.backendType, static_cast<wgpu::BackendType>(0));
EXPECT_EQ(info1.adapterType, static_cast<wgpu::AdapterType>(0));
EXPECT_EQ(info1.vendorID, 0u);
EXPECT_EQ(info1.deviceID, 0u);
EXPECT_EQ(info1.compatibilityMode, false);
}
// Test move construction of the adapter info.
TEST_P(AdapterCreationTest, InfoMoveConstruct) {
wgpu::RequestAdapterOptions options = {};
MockCallback<WGPURequestAdapterCallback> cb;
WGPUAdapter cAdapter = nullptr;
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this))
.WillOnce(SaveArg<1>(&cAdapter));
RequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
if (!adapter) {
return;
}
wgpu::AdapterInfo info1;
adapter.GetInfo(&info1);
wgpu::StringView vendor = info1.vendor;
wgpu::StringView architecture = info1.architecture;
wgpu::StringView device = info1.device;
wgpu::StringView description = info1.description;
wgpu::BackendType backendType = info1.backendType;
wgpu::AdapterType adapterType = info1.adapterType;
uint32_t vendorID = info1.vendorID;
uint32_t deviceID = info1.deviceID;
bool compatibilityMode = info1.compatibilityMode;
wgpu::AdapterInfo info2(std::move(info1));
// Expect info2 to have info1's old contents.
EXPECT_EQ(info2.vendor, vendor);
EXPECT_EQ(info2.architecture, architecture);
EXPECT_EQ(info2.device, device);
EXPECT_EQ(info2.description, description);
EXPECT_EQ(info2.backendType, backendType);
EXPECT_EQ(info2.adapterType, adapterType);
EXPECT_EQ(info2.vendorID, vendorID);
EXPECT_EQ(info2.deviceID, deviceID);
EXPECT_EQ(info2.compatibilityMode, compatibilityMode);
// Expect info1 to be empty.
EXPECT_EQ(info1.vendor.data, nullptr);
EXPECT_EQ(info1.vendor.length, wgpu::kStrlen);
EXPECT_EQ(info1.architecture.data, nullptr);
EXPECT_EQ(info1.architecture.length, wgpu::kStrlen);
EXPECT_EQ(info1.device.data, nullptr);
EXPECT_EQ(info1.device.length, wgpu::kStrlen);
EXPECT_EQ(info1.description.data, nullptr);
EXPECT_EQ(info1.description.length, wgpu::kStrlen);
EXPECT_EQ(info1.backendType, static_cast<wgpu::BackendType>(0));
EXPECT_EQ(info1.adapterType, static_cast<wgpu::AdapterType>(0));
EXPECT_EQ(info1.vendorID, 0u);
EXPECT_EQ(info1.deviceID, 0u);
EXPECT_EQ(info1.compatibilityMode, false);
}
// Test that the adapter info can outlive the adapter.
TEST_P(AdapterCreationTest, InfoOutliveAdapter) {
wgpu::RequestAdapterOptions options = {};
MockCallback<WGPURequestAdapterCallback> cb;
WGPUAdapter cAdapter = nullptr;
EXPECT_CALL(cb, Call(WGPURequestAdapterStatus_Success, _, EmptySizedString(), this))
.WillOnce(SaveArg<1>(&cAdapter));
RequestAdapter(instance, &options, cb.Callback(), cb.MakeUserdata(this));
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
EXPECT_EQ(adapter != nullptr, anyAdapterAvailable);
if (!adapter) {
return;
}
wgpu::AdapterInfo info;
adapter.GetInfo(&info);
// Make a copy of the info.
std::string vendor{std::string_view(info.vendor)};
std::string architecture{std::string_view(info.architecture)};
std::string device{std::string_view(info.device)};
std::string description{std::string_view(info.description)};
// Release the adapter.
adapter = nullptr;
// Ensure we still read the info (pointers are still valid).
// Check the values are equal to make sure they haven't been overwritten,
// and to make sure the compiler can't elide no-op pointer reads.
EXPECT_EQ(std::string_view(info.vendor), vendor);
EXPECT_EQ(std::string_view(info.architecture), architecture);
EXPECT_EQ(std::string_view(info.device), device);
EXPECT_EQ(std::string_view(info.description), description);
}
} // anonymous namespace
} // namespace dawn