blob: 128343f2e6fafd182b5c68ba2639cda008784e8c [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gpu/command_buffer/tests/webgpu_test.h"
#include <dawn/dawn_proc.h>
#include <dawn/dawn_thread_dispatch_proc.h>
#include "base/command_line.h"
#include "base/test/bind.h"
#include "base/test/test_simple_task_runner.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
#include "components/viz/test/test_gpu_service_holder.h"
#include "gpu/command_buffer/client/webgpu_cmd_helper.h"
#include "gpu/command_buffer/client/webgpu_implementation.h"
#include "gpu/command_buffer/service/service_utils.h"
#include "gpu/command_buffer/service/webgpu_decoder.h"
#include "gpu/config/gpu_test_config.h"
#include "gpu/ipc/in_process_command_buffer.h"
#include "gpu/ipc/webgpu_in_process_context.h"
#include "gpu/webgpu/callback.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gpu {
namespace {
void CountCallback(int* count) {
(*count)++;
}
} // anonymous namespace
#define SKIP_TEST_IF(condition) \
if (condition) \
GTEST_SKIP() << #condition
WebGPUTest::Options::Options() = default;
std::map<std::pair<WGPUDevice, wgpu::ErrorType>, /* matched */ bool>
WebGPUTest::s_expected_errors = {};
WebGPUTest::WebGPUTest() = default;
WebGPUTest::~WebGPUTest() = default;
bool WebGPUTest::WebGPUSupported() const {
// Nexus 5X does not support WebGPU
if (GPUTestBotConfig::CurrentConfigMatches("Android Qualcomm 0x4010800")) {
return false;
}
// Pixel 2 does not support WebGPU
if (GPUTestBotConfig::CurrentConfigMatches("Android Qualcomm 0x5040001")) {
return false;
}
return true;
}
bool WebGPUTest::WebGPUSharedImageSupported() const {
// Currently WebGPUSharedImage is only implemented on Mac, Linux, Windows
// and ChromeOS.
#if (BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \
BUILDFLAG(IS_WIN)) && \
BUILDFLAG(USE_DAWN)
// TODO(crbug.com/40166640): Re-enable on AMD when the RX 5500 XT issues are
// resolved.
return !GPUTestBotConfig::CurrentConfigMatches("Linux AMD");
#else
return false;
#endif
}
void WebGPUTest::SetUp() {
SKIP_TEST_IF(!WebGPUSupported());
}
void WebGPUTest::TearDown() {
adapter_ = nullptr;
instance_ = nullptr;
cmd_helper_ = nullptr;
context_ = nullptr;
}
void WebGPUTest::Initialize(const Options& options) {
// Some tests that inherit from WebGPUTest call Initialize in SetUp, which
// won't be skipped even if the SKIP_TEST_IF in WebGPUTest::SetUp() is
// triggered. As a result, to avoid potential crashes, skip initializing if
// this device has been marked as not supporting WebGPU.
if (!WebGPUSupported()) {
return;
}
gpu::GpuPreferences gpu_preferences;
gpu_preferences.enable_webgpu = true;
gpu_preferences.use_passthrough_cmd_decoder =
gles2::UsePassthroughCommandDecoder(
base::CommandLine::ForCurrentProcess());
if (options.use_skia_graphite) {
gpu_preferences.gr_context_type = gpu::GrContextType::kGraphiteDawn;
} else {
#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && BUILDFLAG(USE_DAWN)
gpu_preferences.use_vulkan = gpu::VulkanImplementationName::kNative;
gpu_preferences.gr_context_type = gpu::GrContextType::kVulkan;
#endif
}
gpu_preferences.enable_unsafe_webgpu = options.enable_unsafe_webgpu;
if (!options.adapter_blocklist) {
gpu_preferences.disabled_dawn_features_list = {"adapter_blocklist"};
}
gpu_service_holder_ =
std::make_unique<viz::TestGpuServiceHolder>(gpu_preferences);
context_ = std::make_unique<WebGPUInProcessContext>();
ContextResult result =
context_->Initialize(gpu_service_holder_->task_executor());
ASSERT_EQ(result, ContextResult::kSuccess) << "Context failed to initialize";
cmd_helper_ = std::make_unique<webgpu::WebGPUCmdHelper>(
context_->GetCommandBufferForTest());
webgpu_impl()->SetLostContextCallback(base::BindLambdaForTesting(
[]() { GTEST_FAIL() << "Context lost unexpectedly."; }));
// Use the wire procs for the test main thread.
dawnProcSetPerThreadProcs(&dawn::wire::client::GetProcs());
instance_ = wgpu::Instance(webgpu()->GetAPIChannel()->GetWGPUInstance());
wgpu::RequestAdapterOptions ra_options = {};
ra_options.forceFallbackAdapter = options.force_fallback_adapter;
ra_options.featureLevel = options.feature_level;
bool done = false;
instance_.RequestAdapter(
&ra_options, wgpu::CallbackMode::AllowSpontaneous,
[&](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter,
wgpu::StringView message) {
if (!options.force_fallback_adapter) {
// If we don't force a particular adapter, we
// should always find one.
EXPECT_EQ(status, wgpu::RequestAdapterStatus::Success);
EXPECT_NE(adapter, nullptr);
}
this->adapter_ = std::move(adapter);
done = true;
});
webgpu()->FlushCommands();
while (!done) {
RunPendingTasks();
}
}
webgpu::WebGPUInterface* WebGPUTest::webgpu() const {
return context_->GetImplementation();
}
webgpu::WebGPUImplementation* WebGPUTest::webgpu_impl() const {
return context_->GetImplementation();
}
webgpu::WebGPUCmdHelper* WebGPUTest::webgpu_cmds() const {
return cmd_helper_.get();
}
SharedImageInterface* WebGPUTest::GetSharedImageInterface() const {
return context_->GetCommandBufferForTest()->GetSharedImageInterface();
}
webgpu::WebGPUDecoder* WebGPUTest::GetDecoder() const {
return context_->GetCommandBufferForTest()->GetWebGPUDecoderForTest();
}
void WebGPUTest::RunPendingTasks() {
context_->GetTaskRunner()->RunPendingTasks();
gpu_service_holder_->ScheduleGpuMainTask(base::BindOnce(
[](webgpu::WebGPUDecoder* decoder) {
if (decoder->HasPollingWork()) {
decoder->PerformPollingWork();
}
},
GetDecoder()));
}
void WebGPUTest::WaitForCompletion(wgpu::Device device) {
// Wait for any work submitted to the queue to be finished. The guarantees of
// Dawn are that all previous operations will have been completed and more
// importantly the callbacks will have been called.
wgpu::FutureWaitInfo wait_info = {device.GetQueue().OnSubmittedWorkDone(
wgpu::CallbackMode::WaitAnyOnly,
[](wgpu::QueueWorkDoneStatus, wgpu::StringView) {})};
while (!wait_info.completed) {
instance_.WaitAny(1, &wait_info, 0);
webgpu()->FlushCommands();
RunPendingTasks();
}
}
void WebGPUTest::PollUntilIdle() {
if (!context_ || !gpu_service_holder_) {
// Never initialized. Test skipped or failed in setup.
return;
}
webgpu()->FlushCommands();
base::WaitableEvent wait;
gpu_service_holder_->ScheduleGpuMainTask(
base::BindLambdaForTesting([&wait, decoder = GetDecoder()]() {
while (decoder->HasPollingWork()) {
base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
decoder->PerformPollingWork();
}
wait.Signal();
}));
wait.Wait();
context_->GetTaskRunner()->RunPendingTasks();
}
wgpu::Device WebGPUTest::GetNewDevice(
std::vector<wgpu::FeatureName> requiredFeatures) {
wgpu::Device device;
bool done = false;
DCHECK(adapter_);
wgpu::DeviceDescriptor device_desc = {};
device_desc.requiredFeatureCount = requiredFeatures.size();
device_desc.requiredFeatures = requiredFeatures.data();
device_desc.SetDeviceLostCallback(
wgpu::CallbackMode::AllowSpontaneous,
[](const wgpu::Device&, wgpu::DeviceLostReason reason,
wgpu::StringView message) {
if (reason == wgpu::DeviceLostReason::Destroyed) {
return;
}
GTEST_FAIL() << "Unexpected device lost (" << reason
<< "): " << message;
});
device_desc.SetUncapturedErrorCallback([](const wgpu::Device& device,
wgpu::ErrorType type,
wgpu::StringView message) {
auto it = s_expected_errors.find(std::make_pair(device.Get(), type));
if (it != s_expected_errors.end() && !it->second) {
it->second = true;
return;
}
GTEST_FAIL() << "Unexpected error (" << type << "): " << message;
});
adapter_.RequestDevice(
&device_desc, wgpu::CallbackMode::AllowSpontaneous,
[&](wgpu::RequestDeviceStatus status, wgpu::Device created_device,
wgpu::StringView message) {
// Fail the test with error message if returned status is not success
if (status != wgpu::RequestDeviceStatus::Success) {
if (message.length != 0) {
GTEST_FAIL() << "RequestDevice returns unexpected message: "
<< message;
} else {
GTEST_FAIL()
<< "RequestDevice returns unexpected status without message.";
}
}
device = std::move(created_device);
done = true;
});
webgpu()->FlushCommands();
while (!done) {
base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
RunPendingTasks();
}
EXPECT_NE(device, nullptr);
return device;
}
TEST_F(WebGPUTest, FlushNoCommands) {
Initialize(WebGPUTest::Options());
webgpu()->FlushCommands();
}
// Referred from GLES2ImplementationTest/ReportLoss
TEST_F(WebGPUTest, ReportLoss) {
Initialize(WebGPUTest::Options());
GpuControlClient* webgpu_as_client = webgpu_impl();
int lost_count = 0;
webgpu_impl()->SetLostContextCallback(
base::BindOnce(&CountCallback, &lost_count));
EXPECT_EQ(0, lost_count);
webgpu_as_client->OnGpuControlLostContext();
// The lost context callback should be run when WebGPUImplementation is
// notified of the loss.
EXPECT_EQ(1, lost_count);
}
// Referred from GLES2ImplementationTest/ReportLossReentrant
TEST_F(WebGPUTest, ReportLossReentrant) {
Initialize(WebGPUTest::Options());
GpuControlClient* webgpu_as_client = webgpu_impl();
int lost_count = 0;
webgpu_impl()->SetLostContextCallback(
base::BindOnce(&CountCallback, &lost_count));
EXPECT_EQ(0, lost_count);
webgpu_as_client->OnGpuControlLostContextMaybeReentrant();
// The lost context callback should not be run yet to avoid calling back into
// clients re-entrantly, and having them re-enter WebGPUImplementation.
EXPECT_EQ(0, lost_count);
}
TEST_F(WebGPUTest, RequestAdapterAfterContextLost) {
Initialize(WebGPUTest::Options());
webgpu_impl()->SetLostContextCallback(base::DoNothing());
webgpu_impl()->OnGpuControlLostContext();
bool called = false;
wgpu::RequestAdapterOptions ra_options = {};
instance_.RequestAdapter(
&ra_options, wgpu::CallbackMode::AllowSpontaneous,
[&](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter,
wgpu::StringView message) {
EXPECT_EQ(adapter, nullptr);
called = true;
});
webgpu()->FlushCommands();
RunPendingTasks();
EXPECT_TRUE(called);
}
TEST_F(WebGPUTest, RequestDeviceAfterContextLost) {
Initialize(WebGPUTest::Options());
webgpu_impl()->SetLostContextCallback(base::DoNothing());
webgpu_impl()->OnGpuControlLostContext();
bool called = false;
DCHECK(adapter_);
wgpu::DeviceDescriptor device_desc = {};
adapter_.RequestDevice(&device_desc, wgpu::CallbackMode::AllowSpontaneous,
[&](wgpu::RequestDeviceStatus status,
wgpu::Device device, wgpu::StringView message) {
EXPECT_EQ(device, nullptr);
called = true;
});
webgpu()->FlushCommands();
RunPendingTasks();
EXPECT_TRUE(called);
}
TEST_F(WebGPUTest, RequestDeviceWithUnsupportedFeature) {
Initialize(WebGPUTest::Options());
// Create device with unsupported features, expect to fail to create and
// return nullptr
wgpu::FeatureName invalid_feature = static_cast<wgpu::FeatureName>(-2);
wgpu::Device device;
bool done = false;
DCHECK(adapter_);
wgpu::DeviceDescriptor device_desc = {};
device_desc.requiredFeatureCount = 1;
device_desc.requiredFeatures = &invalid_feature;
adapter_.RequestDevice(
&device_desc, wgpu::CallbackMode::AllowSpontaneous,
[&](wgpu::RequestDeviceStatus status, wgpu::Device created_device,
wgpu::StringView message) {
device = std::move(created_device);
done = true;
});
webgpu()->FlushCommands();
while (!done) {
RunPendingTasks();
}
EXPECT_EQ(device, nullptr);
// Create device again with supported features, expect success and not
// blocked by the last failure
GetNewDevice();
}
TEST_F(WebGPUTest, SPIRVIsDisallowed) {
auto options = WebGPUTest::Options();
options.enable_unsafe_webgpu = false;
Initialize(options);
wgpu::Device device = GetNewDevice();
// Make a invalid ShaderModuleDescriptor because it contains SPIR-V.
wgpu::ShaderSourceSPIRV spirvDesc;
spirvDesc.codeSize = 0;
spirvDesc.code = nullptr;
wgpu::ShaderModuleDescriptor desc;
desc.nextInChain = &spirvDesc;
// Make sure creation fails, and for the correct reason.
device.PushErrorScope(wgpu::ErrorFilter::Validation);
device.CreateShaderModule(&desc);
bool got_error = false;
device.PopErrorScope(wgpu::CallbackMode::AllowSpontaneous,
[&](wgpu::PopErrorScopeStatus status,
wgpu::ErrorType type, wgpu::StringView message) {
// We match on this string to make sure the shader
// module creation fails because SPIR-V is disallowed
// and not because codeSize=0.
EXPECT_THAT(message, testing::HasSubstr("SPIR"));
EXPECT_EQ(type, wgpu::ErrorType::Validation);
got_error = true;
});
WaitForCompletion(device);
EXPECT_TRUE(got_error);
}
TEST_F(WebGPUTest, ExplicitFallbackAdapterIsDisallowed) {
auto options = WebGPUTest::Options();
options.force_fallback_adapter = true;
options.enable_unsafe_webgpu = false;
options.adapter_blocklist = true;
// Initialize attempts to create an adapter.
Initialize(options);
// No fallback adapter should be available.
EXPECT_EQ(adapter_, nullptr);
}
TEST_F(WebGPUTest, ImplicitFallbackAdapterIsDisallowed) {
auto options = WebGPUTest::Options();
options.enable_unsafe_webgpu = false;
// Initialize attempts to create an adapter.
Initialize(options);
if (adapter_) {
wgpu::AdapterInfo info;
adapter_.GetInfo(&info);
// If we got an Adapter, it must not be a CPU adapter.
EXPECT_NE(info.adapterType, wgpu::AdapterType::CPU);
}
}
TEST_F(WebGPUTest, CompatibilityMode) {
auto options = WebGPUTest::Options();
options.feature_level = wgpu::FeatureLevel::Compatibility;
options.enable_unsafe_webgpu = true;
// Initialize attempts to create an adapter.
Initialize(options);
// Compatibility adapter should be available.
EXPECT_NE(adapter_, nullptr);
// A compat defaulting adapter could optionally have the CoreFeaturesAndLimits
// feature.
}
TEST_F(WebGPUTest, NonCompatibilityMode) {
auto options = WebGPUTest::Options();
options.feature_level = wgpu::FeatureLevel::Core;
options.enable_unsafe_webgpu = true;
// Initialize attempts to create an adapter.
Initialize(options);
// Non-compatibility adapter should be available.
EXPECT_NE(adapter_, nullptr);
// A core defaulting adapter must have the CoreFeaturesAndLimits feature.
EXPECT_TRUE(adapter_.HasFeature(wgpu::FeatureName::CoreFeaturesAndLimits));
}
} // namespace gpu