blob: 1ce5b762a67e68adb3dbee76bed324a65afdc74d [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/service/dawn_context_provider.h"
#include <memory>
#include <vector>
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/dcheck_is_on.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/thread_annotations.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_checker.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_dump_provider.h"
#include "base/trace_event/process_memory_dump.h"
#include "base/trace_event/trace_arguments.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/crash/core/common/crash_key.h"
#include "gpu/command_buffer/service/dawn_instance.h"
#include "gpu/command_buffer/service/dawn_platform.h"
#include "gpu/command_buffer/service/skia_utils.h"
#include "gpu/config/gpu_driver_bug_workaround_type.h"
#include "gpu/config/gpu_finch_features.h"
#include "gpu/config/gpu_preferences.h"
#include "gpu/config/gpu_switches.h"
#include "gpu/config/gpu_util.h"
#include "third_party/dawn/include/dawn/webgpu_cpp_print.h"
#include "third_party/skia/include/gpu/graphite/Context.h"
#include "third_party/skia/include/gpu/graphite/dawn/DawnBackendContext.h"
#include "third_party/skia/include/gpu/graphite/dawn/DawnUtils.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/gl_switches.h"
#if BUILDFLAG(IS_WIN)
#include <d3d11_4.h>
#include "third_party/dawn/include/dawn/native/D3D11Backend.h"
#include "ui/gl/direct_composition_support.h"
#include "ui/gl/gl_angle_util_win.h"
#endif
namespace gpu {
namespace {
// Used as a flag to test dawn initialization failure.
BASE_FEATURE(kForceDawnInitializeFailure, base::FEATURE_DISABLED_BY_DEFAULT);
// Sets crash key in thread safe manner. This should be used for any crash keys
// set from dawn error or device lost callbacks that may run on multiple
// threads.
template <uint32_t KeySize>
void SetCrashKeyThreadSafe(crash_reporter::CrashKeyString<KeySize>& crash_key,
std::string_view message) {
static base::NoDestructor<base::Lock> lock;
base::AutoLock auto_lock(*lock.get());
crash_key.Set(message);
}
void SetDawnErrorCrashKey(std::string_view message) {
static crash_reporter::CrashKeyString<1024> error_key("dawn-error");
SetCrashKeyThreadSafe(error_key, message);
}
// Different versions of DumpWithoutCrashing for different reasons.
// Deliberately prevent inlining so that the crash report's call stack can
// distinguish between them.
#if BUILDFLAG(IS_WIN)
NOINLINE NOOPT void DumpWithoutCrashingOnDXGIError(wgpu::ErrorType error_type,
std::string_view message) {
LOG(ERROR) << "DXGI Error: " << message;
if (features::kSkiaGraphiteDawnDumpWCOnD3DError.Get()) {
base::debug::DumpWithoutCrashing();
}
}
NOINLINE NOOPT void DumpWithoutCrashingOnD3D11DebugLayerError(
wgpu::ErrorType error_type,
std::string_view message) {
LOG(ERROR) << message;
if (features::kSkiaGraphiteDawnDumpWCOnD3DError.Get()) {
base::debug::DumpWithoutCrashing();
}
}
#endif
NOINLINE NOOPT void DumpWithoutCrashingOnGenericError(
wgpu::ErrorType error_type,
std::string_view message) {
LOG(ERROR) << message;
base::debug::DumpWithoutCrashing();
}
void DumpWithoutCrashingOnError(wgpu::ErrorType error_type,
std::string_view message) {
SetDawnErrorCrashKey(message);
#if BUILDFLAG(IS_WIN)
if (message.find("DXGI_ERROR") != std::string_view::npos) {
DumpWithoutCrashingOnDXGIError(error_type, message);
} else if (message.find("The D3D11 debug layer") != std::string_view::npos) {
DumpWithoutCrashingOnD3D11DebugLayerError(error_type, message);
} else
#endif
{
DumpWithoutCrashingOnGenericError(error_type, message);
}
}
// NOTE: Update the toggles in GpuInfoCollector whenever a toggle is disabled
// here.
std::vector<const char*> GetDisabledToggles(
const GpuPreferences& gpu_preferences) {
std::vector<const char*> disabled_toggles;
for (const auto& toggle : gpu_preferences.disabled_dawn_features_list) {
disabled_toggles.push_back(toggle.c_str());
}
return disabled_toggles;
}
// NOTE: Update the toggles in GpuInfoCollector whenever a toggle is enabled
// here.
std::vector<const char*> GetEnabledToggles(
wgpu::BackendType backend_type,
bool force_fallback_adapter,
const GpuPreferences& gpu_preferences) {
// If a new toggle is added here, ForceDawnTogglesForSkia() which collects
// info for about:gpu should be updated as well.
std::vector<const char*> enabled_toggles;
for (const auto& toggle : gpu_preferences.enabled_dawn_features_list) {
enabled_toggles.push_back(toggle.c_str());
}
// The following toggles are all device-scoped toggles so it's not necessary
// to pass them when creating the Instance above.
if (features::kSkiaGraphiteDawnBackendDebugLabels.Get()) {
enabled_toggles.push_back("use_user_defined_labels_in_backend");
}
if (features::kSkiaGraphiteDawnSkipValidation.Get()) {
enabled_toggles.push_back("skip_validation");
}
enabled_toggles.push_back("disable_robustness");
enabled_toggles.push_back("disable_lazy_clear_for_mapped_at_creation_buffer");
#if BUILDFLAG(IS_WIN)
if (backend_type == wgpu::BackendType::D3D11) {
// Use packed D24_UNORM_S8_UINT DXGI format for Depth24PlusStencil8
// format.
enabled_toggles.push_back("use_packed_depth24_unorm_stencil8_format");
if (features::kSkiaGraphiteDawnD3D11DelayFlush.Get()) {
// Tell Dawn to defer sending commands to GPU until swapchain's Present.
// This will batch the commands better.
enabled_toggles.push_back("d3d11_delay_flush_to_gpu");
}
}
if (backend_type == wgpu::BackendType::D3D11 ||
backend_type == wgpu::BackendType::D3D12) {
if (features::kSkiaGraphiteDawnDisableD3DShaderOptimizations.Get()) {
enabled_toggles.push_back("d3d_skip_shader_optimizations");
}
}
#endif
if (backend_type == wgpu::BackendType::Vulkan) {
#if BUILDFLAG(IS_ANDROID)
// Enable this toggle for all Android devices suspecting vulkan image size
// mismatch causing SharedTextureMemory creation failures, leading to
// promise image creation failures. See https://crbug.com/377935752 for
// details.
enabled_toggles.push_back(
"ignore_imported_ahardwarebuffer_vulkan_image_size");
#endif
// Use a single VkPipelineCache inside dawn.
enabled_toggles.push_back("vulkan_monolithic_pipeline_cache");
}
// Skip expensive swiftshader vkCmdDraw* for tests.
// TODO(penghuang): rename kDisableGLDrawingForTests to
// kDisableGpuDrawingForTests
// TODO(crbug.com/407497928): Enable this toggle over GpuInfoCollector.
auto* command_line = base::CommandLine::ForCurrentProcess();
if (backend_type == wgpu::BackendType::Vulkan && force_fallback_adapter &&
command_line->HasSwitch(switches::kDisableGLDrawingForTests)) {
enabled_toggles.push_back("vulkan_skip_draw");
}
return enabled_toggles;
}
std::vector<wgpu::FeatureName> GetRequiredFeatures(
wgpu::BackendType backend_type,
wgpu::Adapter adapter) {
std::vector<wgpu::FeatureName> features = {
wgpu::FeatureName::DawnInternalUsages,
wgpu::FeatureName::ImplicitDeviceSynchronization,
#if BUILDFLAG(IS_ANDROID)
wgpu::FeatureName::TextureCompressionETC2,
#endif
};
if (backend_type == wgpu::BackendType::Vulkan) {
#if BUILDFLAG(IS_ANDROID)
features.push_back(wgpu::FeatureName::StaticSamplers);
features.push_back(wgpu::FeatureName::YCbCrVulkanSamplers);
#endif
features.push_back(wgpu::FeatureName::DawnDeviceAllocatorControl);
}
#if BUILDFLAG(IS_WIN)
if (backend_type == wgpu::BackendType::D3D11) {
features.push_back(wgpu::FeatureName::D3D11MultithreadProtected);
}
#endif
constexpr wgpu::FeatureName kOptionalFeatures[] = {
wgpu::FeatureName::BGRA8UnormStorage,
wgpu::FeatureName::BufferMapExtendedUsages,
wgpu::FeatureName::DawnMultiPlanarFormats,
wgpu::FeatureName::DualSourceBlending,
wgpu::FeatureName::FramebufferFetch,
wgpu::FeatureName::MultiPlanarFormatExtendedUsages,
wgpu::FeatureName::MultiPlanarFormatNv16,
wgpu::FeatureName::MultiPlanarFormatNv24,
wgpu::FeatureName::MultiPlanarFormatP010,
wgpu::FeatureName::MultiPlanarFormatP210,
wgpu::FeatureName::MultiPlanarFormatP410,
wgpu::FeatureName::MultiPlanarFormatNv12a,
wgpu::FeatureName::MultiPlanarRenderTargets,
wgpu::FeatureName::Unorm16TextureFormats,
// The following features are always supported by the the Metal backend on
// the Mac versions on which Chrome runs.
wgpu::FeatureName::SharedTextureMemoryIOSurface,
wgpu::FeatureName::SharedFenceMTLSharedEvent,
// The following features are always supported when running on the Vulkan
// backend on Android.
wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer,
wgpu::FeatureName::SharedFenceSyncFD,
// The following features are always supported by the the D3D backends.
wgpu::FeatureName::SharedTextureMemoryD3D11Texture2D,
wgpu::FeatureName::SharedTextureMemoryDXGISharedHandle,
wgpu::FeatureName::SharedFenceDXGISharedHandle,
// The following feature is always supported by the the D3D12 backend.
wgpu::FeatureName::SharedBufferMemoryD3D12Resource,
wgpu::FeatureName::TransientAttachments,
wgpu::FeatureName::DawnLoadResolveTexture,
wgpu::FeatureName::DawnPartialLoadResolveTexture,
wgpu::FeatureName::DawnTexelCopyBufferRowAlignment,
wgpu::FeatureName::FlexibleTextureViews,
};
for (auto feature : kOptionalFeatures) {
if (!adapter.HasFeature(feature)) {
continue;
}
features.push_back(feature);
// Enabling MSAARenderToSingleSampled causes performance regression without
// TransientAttachments support.
if (feature == wgpu::FeatureName::TransientAttachments &&
adapter.HasFeature(wgpu::FeatureName::MSAARenderToSingleSampled)) {
features.push_back(wgpu::FeatureName::MSAARenderToSingleSampled);
}
}
return features;
}
class Platform : public webgpu::DawnPlatform {
public:
using webgpu::DawnPlatform::DawnPlatform;
~Platform() override = default;
const unsigned char* GetTraceCategoryEnabledFlag(
dawn::platform::TraceCategory category) override {
return TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(
TRACE_DISABLED_BY_DEFAULT("gpu.graphite.dawn"));
}
};
#if BUILDFLAG(IS_WIN)
bool GetANGLED3D11DeviceLUID(LUID* luid) {
// On Windows, query the LUID of ANGLE's adapter.
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
gl::QueryD3D11DeviceObjectFromANGLE();
if (!d3d11_device) {
LOG(ERROR) << "Failed to query ID3D11Device from ANGLE.";
return false;
}
Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
if (!SUCCEEDED(d3d11_device.As(&dxgi_device))) {
LOG(ERROR) << "Failed to get IDXGIDevice from ANGLE.";
return false;
}
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
if (!SUCCEEDED(dxgi_device->GetAdapter(&dxgi_adapter))) {
LOG(ERROR) << "Failed to get IDXGIAdapter from ANGLE.";
return false;
}
DXGI_ADAPTER_DESC adapter_desc;
if (!SUCCEEDED(dxgi_adapter->GetDesc(&adapter_desc))) {
LOG(ERROR) << "Failed to get DXGI_ADAPTER_DESC from ANGLE.";
return false;
}
*luid = adapter_desc.AdapterLuid;
return true;
}
bool IsD3D11DebugLayerEnabled(
const Microsoft::WRL::ComPtr<ID3D11Device>& d3d11_device) {
Microsoft::WRL::ComPtr<ID3D11Debug> d3d11_debug;
return SUCCEEDED(d3d11_device.As(&d3d11_debug));
}
const char* HRESULTToString(HRESULT result) {
switch (result) {
#define ERROR_CASE(E) \
case E: \
return #E;
ERROR_CASE(DXGI_ERROR_DEVICE_HUNG)
ERROR_CASE(DXGI_ERROR_DEVICE_REMOVED)
ERROR_CASE(DXGI_ERROR_DEVICE_RESET)
ERROR_CASE(DXGI_ERROR_DRIVER_INTERNAL_ERROR)
ERROR_CASE(DXGI_ERROR_INVALID_CALL)
ERROR_CASE(S_OK)
#undef ERROR_CASE
default:
return nullptr;
}
}
#endif // BUILDFLAG(IS_WIN)
const char* BackendTypeToString(wgpu::BackendType backend_type) {
switch (backend_type) {
case wgpu::BackendType::D3D11:
return "D3D11";
case wgpu::BackendType::D3D12:
return "D3D12";
case wgpu::BackendType::Metal:
return "Metal";
case wgpu::BackendType::Vulkan:
return "Vulkan";
case wgpu::BackendType::OpenGL:
return "OpenGL";
case wgpu::BackendType::OpenGLES:
return "OpenGLES";
default:
CHECK(false);
}
}
} // namespace
// static
wgpu::BackendType DawnContextProvider::GetDefaultBackendType() {
const auto switch_value =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kSkiaGraphiteBackend);
if (switch_value == switches::kSkiaGraphiteBackendDawnD3D11) {
return wgpu::BackendType::D3D11;
} else if (switch_value == switches::kSkiaGraphiteBackendDawnD3D12) {
return wgpu::BackendType::D3D12;
} else if (switch_value == switches::kSkiaGraphiteBackendDawnMetal) {
return wgpu::BackendType::Metal;
} else if (switch_value == switches::kSkiaGraphiteBackendDawnSwiftshader ||
switch_value == switches::kSkiaGraphiteBackendDawnVulkan) {
return wgpu::BackendType::Vulkan;
}
if (gl::GetANGLEImplementation() == gl::ANGLEImplementation::kSwiftShader) {
return wgpu::BackendType::Vulkan;
}
#if BUILDFLAG(IS_WIN)
return base::FeatureList::IsEnabled(features::kSkiaGraphiteDawnUseD3D12)
? wgpu::BackendType::D3D12
: wgpu::BackendType::D3D11;
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
return wgpu::BackendType::Vulkan;
#elif BUILDFLAG(IS_APPLE)
return wgpu::BackendType::Metal;
#else
NOTREACHED();
#endif
}
// static
bool DawnContextProvider::DefaultForceFallbackAdapter() {
return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kSkiaGraphiteBackend) ==
switches::kSkiaGraphiteBackendDawnSwiftshader ||
gl::GetANGLEImplementation() == gl::ANGLEImplementation::kSwiftShader;
}
// static
bool DawnContextProvider::DefaultValidateAdapterFn(wgpu::BackendType,
wgpu::Adapter) {
return true;
}
// Owns the dawn instance/adapter/device so that it's lifetime is not linked to
// a specific DawnContextProvider.
class DawnSharedContext : public base::RefCountedThreadSafe<DawnSharedContext>,
public base::trace_event::MemoryDumpProvider {
public:
DawnSharedContext(gl::ProgressReporter* progress_reporter,
bool thread_safe_graphite_context);
bool Initialize(wgpu::BackendType backend_type,
bool force_fallback_adapter,
const GpuPreferences& gpu_preferences,
const GpuDriverBugWorkarounds& workarounds,
DawnContextProvider::ValidateAdapterFn validate_adapter_fn);
void SetCachingInterface(
std::unique_ptr<webgpu::DawnCachingInterface> caching_interface);
wgpu::Device GetDevice() const { return device_; }
wgpu::BackendType backend_type() const { return backend_type_; }
bool is_vulkan_swiftshader_adapter() const {
return is_vulkan_swiftshader_adapter_;
}
wgpu::Adapter GetAdapter() const { return adapter_; }
wgpu::Instance GetInstance() const { return instance_->Get(); }
webgpu::DawnPlatform* GetDawnPlatform() { return &platform_; }
webgpu::DawnCachingInterface* GetCachingInterface() {
return caching_interface_.get();
}
#if BUILDFLAG(IS_WIN)
Microsoft::WRL::ComPtr<ID3D11Device> GetD3D11Device() const {
if (backend_type() == wgpu::BackendType::D3D11) {
return dawn::native::d3d11::GetD3D11Device(device_.Get());
}
return nullptr;
}
void FlushD3D11CommandsIfDelayed() const {
if (backend_type() != wgpu::BackendType::D3D11) {
return;
}
// This function is meant for delayed flush option.
if (!features::kSkiaGraphiteDawnD3D11DelayFlush.Get()) {
return;
}
TRACE_EVENT0("gpu", "DawnSharedContext::FlushD3D11Commands");
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
dawn::native::d3d11::GetD3D11Device(device_.Get());
if (!d3d11_device) {
return;
}
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
d3d11_device->GetImmediateContext(&context);
context->Flush();
}
#endif
std::optional<error::ContextLostReason> GetResetStatus() const;
std::unique_ptr<GraphiteSharedContext> CreateGraphiteSharedContext(
const skgpu::graphite::ContextOptions& options,
GpuProcessShmCount* use_shader_cache_shm_count,
bool is_thread_safe) {
if (!device_) {
return nullptr;
}
skgpu::graphite::DawnBackendContext backend_context;
backend_context.fInstance = GetInstance();
backend_context.fDevice = device_;
backend_context.fQueue = device_.GetQueue();
std::unique_ptr<skgpu::graphite::Context> graphite_context =
skgpu::graphite::ContextFactory::MakeDawn(backend_context, options);
if (!graphite_context) {
return nullptr;
}
return std::make_unique<GraphiteSharedContext>(
std::move(graphite_context), use_shader_cache_shm_count, is_thread_safe,
features::kSkiaGraphiteMaxPendingRecordings.Get(),
GetBackendFlushCallback());
}
bool use_thread_safe_graphite_context() const {
return use_thread_safe_graphite_context_;
}
GraphiteSharedContext* GetThreadSafeGraphiteSharedContext() const {
CHECK(use_thread_safe_graphite_context());
return thread_safe_graphite_shared_context_.get();
}
bool InitializeThreadSafeGraphiteContext(
const skgpu::graphite::ContextOptions& options,
GpuProcessShmCount* use_shader_cache_shm_count) {
DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
CHECK(use_thread_safe_graphite_context());
CHECK(!thread_safe_graphite_shared_context_);
thread_safe_graphite_shared_context_ = CreateGraphiteSharedContext(
options, use_shader_cache_shm_count, /*is_thread_safe=*/true);
return !!thread_safe_graphite_shared_context_;
}
private:
friend class base::RefCountedThreadSafe<DawnSharedContext>;
// Provided to wgpu::Device as caching callback.
static size_t LoadCachedData(const void* key,
size_t key_size,
void* value,
size_t value_size,
void* userdata) {
if (auto& caching_interface =
static_cast<DawnSharedContext*>(userdata)->caching_interface_) {
return caching_interface->LoadData(key, key_size, value, value_size);
}
return 0;
}
// Provided to wgpu::Device as caching callback.
static void StoreCachedData(const void* key,
size_t key_size,
const void* value,
size_t value_size,
void* userdata) {
if (auto& caching_interface =
static_cast<DawnSharedContext*>(userdata)->caching_interface_) {
caching_interface->StoreData(key, key_size, value, value_size);
}
}
// Provided to wgpu::Device as logging callback.
static void DeviceLogInfo(wgpu::LoggingType type,
wgpu::StringView message,
DawnSharedContext* shared_context) {
CHECK(shared_context);
std::string_view view = {message.data, message.length};
switch (static_cast<wgpu::LoggingType>(type)) {
case wgpu::LoggingType::Warning:
LOG(WARNING) << view;
break;
case wgpu::LoggingType::Error:
// Trigger context loss.
shared_context->OnError(wgpu::ErrorType::Internal, view);
break;
default:
break;
}
}
// Provided to wgpu::Instance as logging callback.
static void InstanceLogInfo(wgpu::LoggingType type,
wgpu::StringView message,
DawnSharedContext* shared_context) {
CHECK(shared_context);
std::string_view view = {message.data, message.length};
if (!shared_context->device_) {
// If device hasn't been created yet. Saving the message so that if there
// is any init failure afterward, we can include the messages in the
// LogInitFailure()'s report.
shared_context->StoreInitLoggingMessage(view);
}
switch (static_cast<wgpu::LoggingType>(type)) {
case wgpu::LoggingType::Warning:
LOG(WARNING) << view;
break;
case wgpu::LoggingType::Error:
LOG(ERROR) << view;
if (shared_context->device_) {
// Only DwC if the device is already created.
// We don't need to DwC for any error before the device is initialized
// because LogInitFailure() would handle them instead.
// Note: We don't trigger context loss here for now since most of the
// errors reported via instance callback is related to Surface
// creation. In that case, instead of triggering context loss, we
// should let the call sites handle them gracefully.
SetDawnErrorCrashKey(view);
base::debug::DumpWithoutCrashing();
}
break;
default:
break;
}
}
~DawnSharedContext() override;
GraphiteSharedContext::FlushCallback GetBackendFlushCallback() {
#if BUILDFLAG(IS_WIN)
return base::BindRepeating(&DawnSharedContext::FlushD3D11CommandsIfDelayed,
base::RetainedRef(this));
#else
return {};
#endif
}
void OnError(wgpu::ErrorType error_type, wgpu::StringView message);
void StoreInitLoggingMessage(std::string_view message) {
init_logging_msgs_.append(message);
init_logging_msgs_.append("\n");
}
void LogInitFailure(std::string_view reason,
bool generate_crash_report,
wgpu::BackendType backend_type,
bool force_fallback_adapter) {
LOG(ERROR) << reason;
if (!generate_crash_report) {
return;
}
SCOPED_CRASH_KEY_STRING256("dawn-shared-context", "init-failure", reason);
SCOPED_CRASH_KEY_STRING32("dawn-shared-context", "backend-type",
BackendTypeToString(backend_type));
SCOPED_CRASH_KEY_BOOL("dawn-shared-context", "fallback-adapter",
force_fallback_adapter);
// Also include any logging messages collected during the initialization.
SCOPED_CRASH_KEY_STRING1024("dawn-shared-context", "init-logging-msgs",
init_logging_msgs_);
init_logging_msgs_.clear();
base::debug::DumpWithoutCrashing();
}
// base::trace_event::MemoryDumpProvider implementation:
bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) override;
std::unique_ptr<webgpu::DawnCachingInterface> caching_interface_;
Platform platform_;
std::unique_ptr<webgpu::DawnInstance> instance_;
wgpu::Adapter adapter_;
wgpu::Device device_;
wgpu::BackendType backend_type_;
// Store logging messages collected during device initialization.
std::string init_logging_msgs_;
bool is_vulkan_swiftshader_adapter_ = false;
bool registered_memory_dump_provider_ = false;
// If true, both GpuMain and CompositorGpuThread share the same
// GraphiteSharedContext which is created lazily. If false,
// DawnContextProvider owns GraphiteSharedContext and each DawnContextProvider
// (i.e. each thread) has its own GraphiteSharedContext.
const bool use_thread_safe_graphite_context_;
std::unique_ptr<GraphiteSharedContext> thread_safe_graphite_shared_context_;
mutable base::Lock context_lost_lock_;
std::optional<error::ContextLostReason> context_lost_reason_
GUARDED_BY(context_lost_lock_);
THREAD_CHECKER(main_thread_checker_);
};
DawnSharedContext::DawnSharedContext(gl::ProgressReporter* progress_reporter,
bool use_thread_safe_graphite_context)
: platform_(/*dawn_caching_interface=*/nullptr,
progress_reporter,
/*uma_prefix=*/"GPU.GraphiteDawn.",
/*record_cache_count_uma=*/true),
use_thread_safe_graphite_context_(use_thread_safe_graphite_context) {
DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
}
DawnSharedContext::~DawnSharedContext() {
DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
if (device_) {
if (registered_memory_dump_provider_) {
base::trace_event::MemoryDumpManager::GetInstance()
->UnregisterDumpProvider(this);
}
device_.SetLoggingCallback([](wgpu::LoggingType, wgpu::StringView) {});
// Destroy GraphiteSharedContext and skgpu::graphite::Context before
// device_, on which skgpu::graphite::Context is created.
thread_safe_graphite_shared_context_ = nullptr;
// Destroy the device now so that the lost callback, which references this
// class, is fired now before we clean up the rest of this class.
device_.Destroy();
}
if (instance_) {
instance_->DisconnectDawnPlatform();
}
}
namespace {
// Dawn Graphite adapter feature level for metrics.
//
// See also: webgpu.h:WGPUFeatureLevel
//
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(DawnAdapterFeatureLevel)
enum class DawnAdapterFeatureLevel {
kUnknown = 0,
kCompatibility = 1,
kCore = 2,
kMaxValue = kCore,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/gpu/enums.xml:DawnAdapterFeatureLevel)
DawnAdapterFeatureLevel DawnAdapterFeatureLevelFromWGPU(
wgpu::FeatureLevel level) {
switch (level) {
case wgpu::FeatureLevel::Compatibility:
return DawnAdapterFeatureLevel::kCompatibility;
case wgpu::FeatureLevel::Core:
return DawnAdapterFeatureLevel::kCore;
default:
return DawnAdapterFeatureLevel::kUnknown;
}
}
} // namespace
bool DawnSharedContext::Initialize(
wgpu::BackendType backend_type,
bool force_fallback_adapter,
const GpuPreferences& gpu_preferences,
const GpuDriverBugWorkarounds& workarounds,
DawnContextProvider::ValidateAdapterFn validate_adapter_fn) {
// Make Dawn experimental API and WGSL features available since access to this
// instance doesn't exit the GPU process.
// LogInfo will be used to receive instance level errors. For example failures
// of loading libraries, initializing backend, etc
dawn::native::DawnInstanceDescriptor dawn_instance_desc;
dawn_instance_desc.SetLoggingCallback(&DawnSharedContext::InstanceLogInfo,
this);
instance_ = webgpu::DawnInstance::Create(&platform_, gpu_preferences,
webgpu::SafetyLevel::kUnsafe,
&dawn_instance_desc);
std::vector<const char*> enabled_toggles =
GetEnabledToggles(backend_type, force_fallback_adapter, gpu_preferences);
std::vector<const char*> disabled_toggles =
GetDisabledToggles(gpu_preferences);
wgpu::DawnTogglesDescriptor toggles_desc;
toggles_desc.enabledToggles = enabled_toggles.data();
toggles_desc.disabledToggles = disabled_toggles.data();
toggles_desc.enabledToggleCount = enabled_toggles.size();
toggles_desc.disabledToggleCount = disabled_toggles.size();
wgpu::RequestAdapterOptions adapter_options;
adapter_options.backendType = backend_type;
adapter_options.forceFallbackAdapter = force_fallback_adapter;
if (workarounds.force_high_performance_gpu) {
adapter_options.powerPreference = wgpu::PowerPreference::HighPerformance;
} else {
adapter_options.powerPreference = wgpu::PowerPreference::LowPower;
}
adapter_options.nextInChain = &toggles_desc;
#if BUILDFLAG(IS_WIN)
dawn::native::d3d::RequestAdapterOptionsLUID adapter_options_luid;
if ((adapter_options.backendType == wgpu::BackendType::D3D11 ||
adapter_options.backendType == wgpu::BackendType::D3D12) &&
GetANGLED3D11DeviceLUID(&adapter_options_luid.adapterLUID)) {
// Request the GPU that ANGLE is using if possible.
adapter_options_luid.nextInChain = adapter_options.nextInChain;
adapter_options.nextInChain = &adapter_options_luid;
}
// Share D3D11 device with ANGLE to reduce synchronization overhead.
dawn::native::d3d11::RequestAdapterOptionsD3D11Device
adapter_options_d3d11_device;
if (adapter_options.backendType == wgpu::BackendType::D3D11) {
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
gl::QueryD3D11DeviceObjectFromANGLE();
CHECK(d3d11_device) << "Query d3d11 device from ANGLE failed.";
static crash_reporter::CrashKeyString<16> feature_level_key(
"d3d11-feature-level");
std::string feature_level =
D3DFeatureLevelToString(d3d11_device->GetFeatureLevel());
feature_level_key.Set(feature_level.c_str());
Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d11_device_context;
d3d11_device->GetImmediateContext(&d3d11_device_context);
Microsoft::WRL::ComPtr<ID3D11Multithread> d3d11_multithread;
HRESULT hr = d3d11_device_context.As(&d3d11_multithread);
CHECK(SUCCEEDED(hr)) << "Query ID3D11Multithread interface failed: 0x"
<< std::hex << hr;
// Dawn requires enable multithread protection for d3d11 device.
d3d11_multithread->SetMultithreadProtected(TRUE);
adapter_options_d3d11_device.device = std::move(d3d11_device);
adapter_options_d3d11_device.nextInChain = adapter_options.nextInChain;
adapter_options.nextInChain = &adapter_options_d3d11_device;
}
#endif // BUILDFLAG(IS_WIN)
adapter_options.featureLevel = wgpu::FeatureLevel::Core;
std::vector<dawn::native::Adapter> adapters =
instance_->EnumerateAdapters(&adapter_options);
if (adapters.empty()) {
LOG(ERROR) << "No adapters found for non compatibility mode.";
adapter_options.featureLevel = wgpu::FeatureLevel::Compatibility;
adapters = instance_->EnumerateAdapters(&adapter_options);
}
if (adapters.empty()) {
// On Android, it's expected that some devices might not support Dawn atm.
// So don't generate report for it.
LogInitFailure("No adapters found.",
/*generate_crash_report=*/!BUILDFLAG(IS_ANDROID),
backend_type, force_fallback_adapter);
return false;
}
adapter_ = wgpu::Adapter(adapters[0].Get());
if (!validate_adapter_fn(backend_type, adapter_)) {
LogInitFailure("Validate adapter failed.",
/*generate_crash_report=*/!BUILDFLAG(IS_ANDROID),
backend_type, force_fallback_adapter);
return false;
}
// Start initializing dawn device here.
wgpu::DawnCacheDeviceDescriptor cache_desc;
cache_desc.loadDataFunction = &DawnSharedContext::LoadCachedData;
cache_desc.storeDataFunction = &DawnSharedContext::StoreCachedData;
// The dawn device is owned by this so a pointer back here is safe.
cache_desc.functionUserdata = this;
cache_desc.nextInChain = &toggles_desc;
wgpu::DawnDeviceAllocatorControl allocator_desc;
wgpu::DeviceDescriptor descriptor;
if (backend_type == wgpu::BackendType::Vulkan) {
// Use a 256kb heap block size in the Vulkan backend to minimize
// fragmentation.
allocator_desc.allocatorHeapBlockSize = 256 * 1024;
allocator_desc.nextInChain = &cache_desc;
descriptor.nextInChain = &allocator_desc;
} else {
descriptor.nextInChain = &cache_desc;
}
descriptor.SetUncapturedErrorCallback(
[](const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView message,
DawnSharedContext* state) {
if (type != wgpu::ErrorType::NoError) {
state->OnError(type, message);
}
},
this);
descriptor.SetDeviceLostCallback(
wgpu::CallbackMode::AllowSpontaneous,
[](const wgpu::Device&, wgpu::DeviceLostReason reason,
wgpu::StringView message, DawnSharedContext* state) {
if (reason != wgpu::DeviceLostReason::Destroyed) {
state->OnError(wgpu::ErrorType::Unknown, message);
}
},
this);
std::vector<wgpu::FeatureName> features =
GetRequiredFeatures(backend_type, adapter_);
descriptor.requiredFeatures = features.data();
descriptor.requiredFeatureCount = std::size(features);
// Use best limits for the device.
wgpu::Limits supportedLimits = {};
if (adapter_.GetLimits(&supportedLimits) != wgpu::Status::Success) {
LogInitFailure("Failed to call adapter.GetLimits().",
/*generate_crash_report=*/true, backend_type,
force_fallback_adapter);
return false;
}
descriptor.requiredLimits = &supportedLimits;
// ANGLE always tries creating D3D11 device with debug layer when dcheck is
// on, so tries creating dawn device with backend validation as well.
constexpr bool enable_backend_validation =
DCHECK_IS_ON() && BUILDFLAG(IS_WIN);
std::vector<dawn::native::BackendValidationLevel> backend_validation_levels =
{dawn::native::BackendValidationLevel::Disabled};
if (features::kSkiaGraphiteDawnBackendValidation.Get() ||
enable_backend_validation) {
backend_validation_levels.push_back(
dawn::native::BackendValidationLevel::Partial);
backend_validation_levels.push_back(
dawn::native::BackendValidationLevel::Full);
}
if (base::FeatureList::IsEnabled(kForceDawnInitializeFailure)) {
LOG(ERROR) << "DawnSharedContext creation failed for testing";
return false;
}
// Try create device with backend validation level.
for (auto it = backend_validation_levels.rbegin();
it != backend_validation_levels.rend(); ++it) {
auto level = *it;
instance_->SetBackendValidationLevel(level);
device_ = adapter_.CreateDevice(&descriptor);
if (device_) {
break;
}
}
if (!device_) {
LogInitFailure("Failed to create device.", /*generate_crash_report=*/true,
backend_type, force_fallback_adapter);
return false;
}
device_.SetLoggingCallback(&DawnSharedContext::DeviceLogInfo, this);
backend_type_ = backend_type;
is_vulkan_swiftshader_adapter_ =
backend_type == wgpu::BackendType::Vulkan && force_fallback_adapter;
#if BUILDFLAG(IS_WIN)
if (auto d3d11_device = GetD3D11Device()) {
static auto* crash_key = base::debug::AllocateCrashKeyString(
"d3d11-debug-layer", base::debug::CrashKeySize::Size32);
const bool enabled = IsD3D11DebugLayerEnabled(d3d11_device);
base::debug::SetCrashKeyString(crash_key, enabled ? "enabled" : "disabled");
}
#endif // BUILDFLAG(IS_WIN)
if (base::SingleThreadTaskRunner::HasCurrentDefault()) {
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
this, "DawnSharedContext",
base::SingleThreadTaskRunner::GetCurrentDefault());
registered_memory_dump_provider_ = true;
}
base::UmaHistogramEnumeration(
"GPU.Dawn.AdapterFeatureLevel",
DawnAdapterFeatureLevelFromWGPU(adapter_options.featureLevel));
return true;
}
void DawnSharedContext::SetCachingInterface(
std::unique_ptr<webgpu::DawnCachingInterface> caching_interface) {
CHECK(!caching_interface_);
caching_interface_ = std::move(caching_interface);
}
std::optional<error::ContextLostReason> DawnSharedContext::GetResetStatus()
const {
base::AutoLock auto_lock(context_lost_lock_);
return context_lost_reason_;
}
void DawnSharedContext::OnError(wgpu::ErrorType error_type,
wgpu::StringView message) {
#if BUILDFLAG(IS_WIN)
if (auto d3d11_device = GetD3D11Device()) {
static crash_reporter::CrashKeyString<64> reason_message_key(
"d3d11-device-removed-reason");
HRESULT result = d3d11_device->GetDeviceRemovedReason();
if (const char* result_string = HRESULTToString(result)) {
LOG(ERROR) << "Device removed reason: " << result_string;
SetCrashKeyThreadSafe(reason_message_key, result_string);
} else {
auto unknown_error = base::StringPrintf("Unknown error(0x%08lX)", result);
LOG(ERROR) << "Device removed reason: " << unknown_error;
SetCrashKeyThreadSafe(reason_message_key, unknown_error.c_str());
}
}
#endif
DumpWithoutCrashingOnError(error_type,
static_cast<std::string_view>(message));
#if !DCHECK_IS_ON()
// Do not provoke context loss on validation failures for non-DCHECK builds.
// We want to capture the above dump on validation errors, but not necessarily
// restart the GPU process unless we also have a device loss.
if (error_type == wgpu::ErrorType::Validation) {
return;
}
#endif
base::AutoLock auto_lock(context_lost_lock_);
if (context_lost_reason_.has_value()) {
return;
}
switch (error_type) {
case wgpu::ErrorType::OutOfMemory:
context_lost_reason_ = error::kOutOfMemory;
break;
case wgpu::ErrorType::Validation:
context_lost_reason_ = error::kGuilty;
break;
default:
context_lost_reason_ = error::kUnknown;
break;
}
}
namespace {
static constexpr char kDawnMemoryDumpPrefix[] = "gpu/dawn";
static constexpr char kAllocatorMemoryDumpPrefix[] =
"gpu/vulkan/graphite_allocator";
class DawnMemoryDump : public dawn::native::MemoryDump {
public:
explicit DawnMemoryDump(base::trace_event::ProcessMemoryDump* pmd)
: pmd_(pmd) {
CHECK(pmd_);
}
~DawnMemoryDump() override = default;
void AddScalar(const char* name,
const char* key,
const char* units,
uint64_t value) override {
pmd_->GetOrCreateAllocatorDump(
base::JoinString({kDawnMemoryDumpPrefix, name}, "/"))
->AddScalar(key, units, value);
}
void AddString(const char* name,
const char* key,
const std::string& value) override {
pmd_->GetOrCreateAllocatorDump(
base::JoinString({kDawnMemoryDumpPrefix, name}, "/"))
->AddString(key, "", value);
}
private:
const raw_ptr<base::trace_event::ProcessMemoryDump> pmd_;
};
} // namespace
bool DawnSharedContext::OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) {
using base::trace_event::MemoryAllocatorDump;
if (args.level_of_detail ==
base::trace_event::MemoryDumpLevelOfDetail::kBackground) {
const dawn::native::MemoryUsageInfo mem_usage =
dawn::native::ComputeEstimatedMemoryUsageInfo(device_.Get());
pmd->GetOrCreateAllocatorDump(kDawnMemoryDumpPrefix)
->AddScalar(MemoryAllocatorDump::kNameSize,
MemoryAllocatorDump::kUnitsBytes, mem_usage.totalUsage);
pmd->GetOrCreateAllocatorDump(
base::JoinString({kDawnMemoryDumpPrefix, "textures"}, "/"))
->AddScalar(MemoryAllocatorDump::kNameSize,
MemoryAllocatorDump::kUnitsBytes, mem_usage.texturesUsage);
pmd
->GetOrCreateAllocatorDump(base::JoinString(
{kDawnMemoryDumpPrefix, "textures/depth_stencil"}, "/"))
->AddScalar(MemoryAllocatorDump::kNameSize,
MemoryAllocatorDump::kUnitsBytes,
mem_usage.depthStencilTexturesUsage);
auto* msaa_dump = pmd->GetOrCreateAllocatorDump(
base::JoinString({kDawnMemoryDumpPrefix, "textures/msaa"}, "/"));
msaa_dump->AddScalar(MemoryAllocatorDump::kNameSize,
MemoryAllocatorDump::kUnitsBytes,
mem_usage.msaaTexturesUsage);
msaa_dump->AddScalar(MemoryAllocatorDump::kNameObjectCount,
MemoryAllocatorDump::kUnitsObjects,
mem_usage.msaaTexturesCount);
msaa_dump->AddScalar("biggest_size", MemoryAllocatorDump::kUnitsBytes,
mem_usage.largestMsaaTextureUsage);
pmd->GetOrCreateAllocatorDump(
base::JoinString({kDawnMemoryDumpPrefix, "buffers"}, "/"))
->AddScalar(MemoryAllocatorDump::kNameSize,
MemoryAllocatorDump::kUnitsBytes, mem_usage.buffersUsage);
} else {
DawnMemoryDump dawnMemoryDump(pmd);
dawn::native::DumpMemoryStatistics(device_.Get(), &dawnMemoryDump);
}
if (backend_type() == wgpu::BackendType::Vulkan) {
// For Graphite-Vulkan backend, report vulkan allocator dumps and
// statistics.
auto* dump = pmd->GetOrCreateAllocatorDump(kAllocatorMemoryDumpPrefix);
const dawn::native::AllocatorMemoryInfo allocator_usage =
dawn::native::GetAllocatorMemoryInfo(device_.Get());
// `allocated_size` is memory allocated from the device, used is what is
// actually used.
dump->AddScalar("allocated_size", MemoryAllocatorDump::kUnitsBytes,
allocator_usage.totalAllocatedMemory -
allocator_usage.totalLazyAllocatedMemory);
dump->AddScalar(
"used_size", MemoryAllocatorDump::kUnitsBytes,
allocator_usage.totalUsedMemory - allocator_usage.totalLazyUsedMemory);
dump->AddScalar(
"fragmentation_size", MemoryAllocatorDump::kUnitsBytes,
allocator_usage.totalAllocatedMemory - allocator_usage.totalUsedMemory);
dump->AddScalar("lazy_allocated_size", MemoryAllocatorDump::kUnitsBytes,
allocator_usage.totalLazyAllocatedMemory);
dump->AddScalar("lazy_used_size", MemoryAllocatorDump::kUnitsBytes,
allocator_usage.totalLazyUsedMemory);
}
return true;
}
std::unique_ptr<DawnContextProvider> DawnContextProvider::Create(
const GpuPreferences& gpu_preferences,
const GpuFeatureInfo& gpu_feature_info,
gl::ProgressReporter* progress_reporter,
ValidateAdapterFn validate_adapter_fn) {
return DawnContextProvider::CreateWithBackend(
GetDefaultBackendType(), DefaultForceFallbackAdapter(), gpu_preferences,
gpu_feature_info, progress_reporter, validate_adapter_fn);
}
std::unique_ptr<DawnContextProvider> DawnContextProvider::CreateWithBackend(
wgpu::BackendType backend_type,
bool force_fallback_adapter,
const GpuPreferences& gpu_preferences,
const GpuFeatureInfo& gpu_feature_info,
gl::ProgressReporter* progress_reporter,
ValidateAdapterFn validate_adapter_fn) {
bool use_thread_safe_graphite_context =
features::IsDrDcEnabled(gpu_feature_info) &&
features::IsGraphiteContextThreadSafe();
auto dawn_shared_context = base::MakeRefCounted<DawnSharedContext>(
progress_reporter, use_thread_safe_graphite_context);
GpuDriverBugWorkarounds workarounds(
gpu_feature_info.enabled_gpu_driver_bug_workarounds);
if (!dawn_shared_context->Initialize(backend_type, force_fallback_adapter,
gpu_preferences, workarounds,
validate_adapter_fn)) {
return nullptr;
}
return base::WrapUnique(
new DawnContextProvider(std::move(dawn_shared_context)));
}
std::unique_ptr<DawnContextProvider>
DawnContextProvider::CreateWithSharedDevice(
const DawnContextProvider* existing) {
CHECK(existing);
CHECK(existing->dawn_shared_context_);
return base::WrapUnique(
new DawnContextProvider(existing->dawn_shared_context_));
}
DawnContextProvider::DawnContextProvider(
scoped_refptr<DawnSharedContext> dawn_shared_context)
: dawn_shared_context_(std::move(dawn_shared_context)) {
CHECK(dawn_shared_context_);
}
DawnContextProvider::~DawnContextProvider() = default;
wgpu::Device DawnContextProvider::GetDevice() const {
return dawn_shared_context_->GetDevice();
}
wgpu::BackendType DawnContextProvider::backend_type() const {
return dawn_shared_context_->backend_type();
}
bool DawnContextProvider::is_vulkan_swiftshader_adapter() const {
return dawn_shared_context_->is_vulkan_swiftshader_adapter();
}
wgpu::Adapter DawnContextProvider::GetAdapter() const {
return dawn_shared_context_->GetAdapter();
}
wgpu::Instance DawnContextProvider::GetInstance() const {
return dawn_shared_context_->GetInstance();
}
bool DawnContextProvider::use_thread_safe_shared_context() const {
return dawn_shared_context_->use_thread_safe_graphite_context();
}
void DawnContextProvider::InitializeThreadSafeGraphiteContext(
const skgpu::graphite::ContextOptions& options,
GpuProcessShmCount* use_shader_cache_shm_count) {
dawn_shared_context_->InitializeThreadSafeGraphiteContext(
options, use_shader_cache_shm_count);
}
bool DawnContextProvider::InitializeGraphiteContext(
const skgpu::graphite::ContextOptions& options,
GpuProcessShmCount* use_shader_cache_shm_count) {
if (dawn_shared_context_->use_thread_safe_graphite_context()) {
return !!dawn_shared_context_->GetThreadSafeGraphiteSharedContext();
}
graphite_shared_context_ = dawn_shared_context_->CreateGraphiteSharedContext(
options, use_shader_cache_shm_count, /*is_thread_safe=*/false);
return !!graphite_shared_context_;
}
void DawnContextProvider::SetCachingInterface(
std::unique_ptr<webgpu::DawnCachingInterface> caching_interface) {
CHECK(dawn_shared_context_->HasOneRef());
CHECK(!graphite_shared_context_);
dawn_shared_context_->SetCachingInterface(std::move(caching_interface));
}
webgpu::DawnCachingInterface* DawnContextProvider::GetCachingInterface() const {
return dawn_shared_context_->GetCachingInterface();
}
#if BUILDFLAG(IS_WIN)
Microsoft::WRL::ComPtr<ID3D11Device> DawnContextProvider::GetD3D11Device()
const {
return dawn_shared_context_->GetD3D11Device();
}
#endif
bool DawnContextProvider::SupportsFeature(wgpu::FeatureName feature) {
return dawn_shared_context_->GetDevice().HasFeature(feature);
}
std::optional<error::ContextLostReason> DawnContextProvider::GetResetStatus()
const {
return dawn_shared_context_->GetResetStatus();
}
GraphiteSharedContext* DawnContextProvider::GetGraphiteSharedContext() const {
if (dawn_shared_context_->use_thread_safe_graphite_context()) {
// Both threads shares the same GraphiteSharedContext. DawnSharedContext
// owns GraphiteSharedContext
return dawn_shared_context_->GetThreadSafeGraphiteSharedContext();
} else {
// Each DawnContextProvider owns its own GraphiteSharedContext and
// skgpu::graphite::Context
return graphite_shared_context_.get();
}
}
webgpu::DawnPlatform* DawnContextProvider::GetDawnPlatform() {
return dawn_shared_context_->GetDawnPlatform();
}
} // namespace gpu