blob: cff1d34f299dad5d8806f2e858a5a5de2388d3ef [file]
// Copyright 2016 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/vulkan_in_process_context_provider.h"
#include <algorithm>
#include <string_view>
#include <utility>
#include "base/compiler_specific.h"
#include "base/memory_coordinator/memory_consumer.h"
#include "base/memory_coordinator/memory_coordinator_features.h"
#include "base/memory_coordinator/memory_limit.h"
#include "base/memory_coordinator/traits.h"
#include "base/memory_coordinator/utils.h"
#include "base/numerics/safe_conversions.h"
#include "gpu/vulkan/buildflags.h"
#include "gpu/vulkan/vma_wrapper.h"
#include "gpu/vulkan/vulkan_device_queue.h"
#include "gpu/vulkan/vulkan_fence_helper.h"
#include "gpu/vulkan/vulkan_function_pointers.h"
#include "gpu/vulkan/vulkan_implementation.h"
#include "gpu/vulkan/vulkan_instance.h"
#include "gpu/vulkan/vulkan_util.h"
#include "third_party/skia/include/gpu/ganesh/GrDirectContext.h"
#include "third_party/skia/include/gpu/ganesh/vk/GrVkDirectContext.h"
#include "third_party/skia/include/gpu/vk/VulkanBackendContext.h"
#include "third_party/skia/include/gpu/vk/VulkanExtensions.h"
#include "third_party/skia/include/gpu/vk/VulkanTypes.h"
namespace {
// Setting this limit to 0 practically forces sync at every submit.
constexpr uint32_t kSyncCpuMemoryLimitAtMemoryPressureCritical = 0;
constexpr base::MemoryConsumerTraits kMemoryConsumerTraits(
// Deferred Vulkan resources can accumulate to a moderate amount of memory.
base::MemoryConsumerTraits::EstimatedMemoryUsage::kMedium,
// Callback only updates atomics without traversing structures.
base::MemoryConsumerTraits::ReleaseMemoryCost::kFreesPagesWithoutTraversal,
// Syncing GPU work does not lose user state or cached data.
base::MemoryConsumerTraits::InformationRetention::kLossless,
// Pre-determined by AsyncMemoryConsumerRegistration on the GPU thread.
base::MemoryConsumerTraits::ExecutionType::kAsynchronous);
} // namespace
namespace gpu {
// static
scoped_refptr<VulkanInProcessContextProvider>
VulkanInProcessContextProvider::Create(
VulkanImplementation* vulkan_implementation,
uint32_t heap_memory_limit,
uint32_t sync_cpu_memory_limit,
const bool is_thread_safe,
const GPUInfo* gpu_info,
base::TimeDelta cooldown_duration_at_memory_pressure_critical) {
scoped_refptr<VulkanInProcessContextProvider> context_provider(
new VulkanInProcessContextProvider(
vulkan_implementation, heap_memory_limit, sync_cpu_memory_limit,
cooldown_duration_at_memory_pressure_critical));
if (!context_provider->Initialize(gpu_info, is_thread_safe)) {
return nullptr;
}
return context_provider;
}
scoped_refptr<VulkanInProcessContextProvider>
VulkanInProcessContextProvider::CreateForCompositorGpuThread(
VulkanImplementation* vulkan_implementation,
std::unique_ptr<VulkanDeviceQueue> vulkan_device_queue,
uint32_t sync_cpu_memory_limit,
base::TimeDelta cooldown_duration_at_memory_pressure_critical) {
if (!vulkan_implementation) {
return nullptr;
}
scoped_refptr<VulkanInProcessContextProvider> context_provider(
new VulkanInProcessContextProvider(
vulkan_implementation, /*heap_memory_limit=*/0, sync_cpu_memory_limit,
cooldown_duration_at_memory_pressure_critical));
context_provider->InitializeForCompositorGpuThread(
std::move(vulkan_device_queue));
return context_provider;
}
VulkanInProcessContextProvider::VulkanInProcessContextProvider(
VulkanImplementation* vulkan_implementation,
uint32_t heap_memory_limit,
uint32_t sync_cpu_memory_limit,
base::TimeDelta cooldown_duration_at_memory_pressure_critical)
: vulkan_implementation_(vulkan_implementation),
heap_memory_limit_(heap_memory_limit),
sync_cpu_memory_limit_(sync_cpu_memory_limit > 0
? std::make_optional(sync_cpu_memory_limit)
: std::nullopt),
cooldown_duration_at_memory_pressure_critical_(
cooldown_duration_at_memory_pressure_critical),
critical_memory_pressure_expiration_time_(base::TimeTicks()),
active_sync_cpu_memory_limit_(sync_cpu_memory_limit) {
memory_consumer_registration_ =
std::make_unique<base::AsyncMemoryConsumerRegistration>(
"VulkanInProcessContextProvider", kMemoryConsumerTraits, this);
}
VulkanInProcessContextProvider::~VulkanInProcessContextProvider() {
Destroy();
}
bool VulkanInProcessContextProvider::Initialize(const GPUInfo* gpu_info,
const bool is_thread_safe) {
DCHECK(!device_queue_);
const auto& instance_extensions = vulkan_implementation_->GetVulkanInstance()
->vulkan_info()
.enabled_instance_extensions;
uint32_t flags = VulkanDeviceQueue::GRAPHICS_QUEUE_FLAG;
constexpr std::string_view surface_extension_name(
VK_KHR_SURFACE_EXTENSION_NAME);
for (const auto* extension : instance_extensions) {
if (surface_extension_name == extension) {
flags |= VulkanDeviceQueue::PRESENTATION_SUPPORT_QUEUE_FLAG;
break;
}
}
device_queue_ =
CreateVulkanDeviceQueue(vulkan_implementation_, flags, gpu_info,
heap_memory_limit_, is_thread_safe);
if (!device_queue_) {
return false;
}
return true;
}
void VulkanInProcessContextProvider::InitializeForCompositorGpuThread(
std::unique_ptr<VulkanDeviceQueue> vulkan_device_queue) {
DCHECK(!device_queue_);
DCHECK(vulkan_device_queue);
device_queue_ = std::move(vulkan_device_queue);
}
bool VulkanInProcessContextProvider::InitializeGrContext(
const GrContextOptions& context_options) {
skgpu::VulkanBackendContext backend_context;
backend_context.fInstance = device_queue_->GetVulkanInstance();
backend_context.fPhysicalDevice = device_queue_->GetVulkanPhysicalDevice();
backend_context.fDevice = device_queue_->GetVulkanDevice();
backend_context.fQueue = device_queue_->GetVulkanQueue();
backend_context.fGraphicsQueueIndex = device_queue_->GetVulkanQueueIndex();
backend_context.fMaxAPIVersion = vulkan_implementation_->GetVulkanInstance()
->vulkan_info()
.used_api_version;
backend_context.fMemoryAllocator = device_queue_->GetSkiaVkMemoryAllocator();
skgpu::VulkanGetProc get_proc = [](const char* proc_name, VkInstance instance,
VkDevice device) {
if (device) {
// Using vkQueue*Hook for all vkQueue* methods here to make both chrome
// side access and skia side access to the same queue thread safe.
// vkQueue*Hook routes all skia side access to the same
// VulkanFunctionPointers vkQueue* api which chrome uses and is under the
// lock.
std::string_view proc_name_view(proc_name);
if (proc_name_view == "vkCreateGraphicsPipelines") {
return reinterpret_cast<PFN_vkVoidFunction>(
&CreateGraphicsPipelinesHook);
} else if (proc_name_view == "vkQueueSubmit") {
return reinterpret_cast<PFN_vkVoidFunction>(&VulkanQueueSubmitHook);
} else if (proc_name_view == "vkQueueWaitIdle") {
return reinterpret_cast<PFN_vkVoidFunction>(&VulkanQueueWaitIdleHook);
} else if (proc_name_view == "vkQueuePresentKHR") {
return reinterpret_cast<PFN_vkVoidFunction>(&VulkanQueuePresentKHRHook);
}
return vkGetDeviceProcAddr(device, proc_name);
}
return vkGetInstanceProcAddr(instance, proc_name);
};
const auto& instance_extensions = vulkan_implementation_->GetVulkanInstance()
->vulkan_info()
.enabled_instance_extensions;
std::vector<const char*> device_extensions;
device_extensions.reserve(device_queue_->enabled_extensions().size());
for (const auto& extension : device_queue_->enabled_extensions()) {
device_extensions.push_back(extension.data());
}
skgpu::VulkanExtensions vk_extensions;
vk_extensions.init(get_proc,
vulkan_implementation_->GetVulkanInstance()->vk_instance(),
device_queue_->GetVulkanPhysicalDevice(),
instance_extensions.size(), instance_extensions.data(),
device_extensions.size(), device_extensions.data());
backend_context.fVkExtensions = &vk_extensions;
backend_context.fDeviceFeatures2 =
&device_queue_->enabled_device_features_2();
backend_context.fGetProc = get_proc;
backend_context.fProtectedContext = GrProtected::kNo;
gr_context_ = GrDirectContexts::MakeVulkan(backend_context, context_options);
return gr_context_ != nullptr;
}
void VulkanInProcessContextProvider::Destroy() {
if (device_queue_) {
// Destroy |fence_helper| will wait idle on the device queue, and then run
// all enqueued cleanup tasks.
auto* fence_helper = device_queue_->GetFenceHelper();
fence_helper->Destroy();
}
if (gr_context_) {
// releaseResourcesAndAbandonContext() will wait on GPU to finish all works,
// execute pending flush done callbacks and release all resources.
gr_context_->releaseResourcesAndAbandonContext();
gr_context_.reset();
}
if (device_queue_) {
device_queue_->Destroy();
device_queue_.reset();
}
}
VulkanImplementation*
VulkanInProcessContextProvider::GetVulkanImplementation() {
return vulkan_implementation_;
}
VulkanDeviceQueue* VulkanInProcessContextProvider::GetDeviceQueue() {
return device_queue_.get();
}
GrDirectContext* VulkanInProcessContextProvider::GetGrContext() {
return gr_context_.get();
}
GrVkSecondaryCBDrawContext*
VulkanInProcessContextProvider::GetGrSecondaryCBDrawContext() {
return nullptr;
}
void VulkanInProcessContextProvider::EnqueueSecondaryCBSemaphores(
std::vector<VkSemaphore> semaphores) {
NOTREACHED();
}
void VulkanInProcessContextProvider::EnqueueSecondaryCBPostSubmitTask(
base::OnceClosure closure) {
NOTREACHED();
}
std::optional<uint32_t> VulkanInProcessContextProvider::GetSyncCpuMemoryLimit()
const {
// Return nullopt to indicate that there's no limit.
if (!sync_cpu_memory_limit_) {
return std::nullopt;
}
if (base::FeatureList::IsEnabled(base::kStatefulMemoryPressure)) {
return active_sync_cpu_memory_limit_.load(std::memory_order_relaxed);
}
return base::TimeTicks::Now() <
critical_memory_pressure_expiration_time_.load(
std::memory_order_relaxed)
? std::optional<uint32_t>(
kSyncCpuMemoryLimitAtMemoryPressureCritical)
: sync_cpu_memory_limit_;
}
uint32_t VulkanInProcessContextProvider::GetCurrentGpuMemoryUsage() const {
if (device_queue_) {
return base::saturated_cast<uint32_t>(
vma::GetTotalAllocatedAndUsedMemory(device_queue_->vma_allocator())
.first);
}
return 0;
}
void VulkanInProcessContextProvider::OnUpdateMemoryLimit() {
if (!sync_cpu_memory_limit_) {
return;
}
if (base::FeatureList::IsEnabled(base::kStatefulMemoryPressure)) {
// We cap the ratio to 1.0 to ensure that we never exceed the
// user-provided sync_cpu_memory_limit, even if the memory coordinator
// allows for more memory usage (ratio > 1.0).
base::MemoryLimit capped_memory_limit =
std::min(base::MemoryLimit::NoPressureThreshold(), memory_limit());
uint32_t target_limit = capped_memory_limit.Scale(*sync_cpu_memory_limit_);
uint32_t new_limit = target_limit;
uint32_t current_active =
active_sync_cpu_memory_limit_.load(std::memory_order_relaxed);
if (target_limit <= current_active) {
// Limit decreased: cap at current usage to prevent growth without
// triggering immediate eviction.
new_limit = std::max(GetCurrentGpuMemoryUsage(), target_limit);
}
active_sync_cpu_memory_limit_.store(new_limit, std::memory_order_relaxed);
}
}
void VulkanInProcessContextProvider::OnReleaseMemory() {
if (!sync_cpu_memory_limit_) {
return;
}
if (base::FeatureList::IsEnabled(base::kStatefulMemoryPressure)) {
// We cap the ratio to 1.0 to ensure that we never exceed the
// user-provided sync_cpu_memory_limit, even if the memory coordinator
// allows for more memory usage (ratio > 1.0).
base::MemoryLimit capped_memory_limit =
std::min(base::MemoryLimit::NoPressureThreshold(), memory_limit());
uint32_t new_sync_cpu_memory_limit =
capped_memory_limit.Scale(*sync_cpu_memory_limit_);
active_sync_cpu_memory_limit_.store(new_sync_cpu_memory_limit,
std::memory_order_relaxed);
return;
}
if (memory_limit() > base::MemoryLimit::CriticalPressureThreshold()) {
return;
}
critical_memory_pressure_expiration_time_.store(
base::TimeTicks::Now() + cooldown_duration_at_memory_pressure_critical_,
std::memory_order_relaxed);
}
} // namespace gpu