// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/viz/host/gpu_host_impl.h"

#include <utility>

#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/no_destructor.h"
#include "base/process/process_handle.h"
#include "base/strings/strcat.h"
#include "base/system/sys_info.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/thread_checker.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/viz/common/buildflags.h"
#include "components/viz/common/features.h"
#include "components/viz/host/persistent_cache_sandboxed_file_factory.h"
#include "gpu/config/gpu_driver_bug_workaround_type.h"
#include "gpu/config/gpu_feature_info.h"
#include "gpu/config/gpu_finch_features.h"
#include "gpu/config/gpu_info.h"
#include "gpu/ipc/common/gpu_client_ids.h"
#include "gpu/ipc/host/gpu_disk_cache.h"
#include "gpu/webgpu/dawn_commit_hash.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#include "services/webnn/host/weights_file_provider.h"
#include "skia/buildflags.h"
#include "skia/ext/skia_commit_hash.h"
#include "ui/gfx/font_render_params.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/android_info.h"
#endif

#if BUILDFLAG(IS_WIN)
#include "services/webnn/host/execution_provider_initializer.h"
#include "services/webnn/public/cpp/context_properties.h"
#include "services/webnn/public/cpp/ep_device_info.h"
#include "ui/gfx/win/rendering_window_manager.h"
#elif BUILDFLAG(IS_MAC)
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#endif

#if BUILDFLAG(IS_OZONE)
#include "base/time/time.h"
#include "ui/ozone/public/gpu_platform_support_host.h"
#include "ui/ozone/public/ozone_platform.h"
#endif

namespace viz {
namespace {

// A wrapper around gfx::FontRenderParams that checks it is set and accessed on
// the same thread.
class FontRenderParams {
 public:
  FontRenderParams(const FontRenderParams&) = delete;
  FontRenderParams& operator=(const FontRenderParams&) = delete;

  void Set(const gfx::FontRenderParams& params) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    params_ = params;
    if (gpu_host_impl_) {
      gpu_host_impl_->MaybeSendFontRenderParams();
    }
  }

  const std::optional<gfx::FontRenderParams>& Get() {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    return params_;
  }

  void SetGpuHostImpl(GpuHostImpl* gpu_host_impl) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    gpu_host_impl_ = gpu_host_impl;
  }

 private:
  friend class base::NoDestructor<FontRenderParams>;

  FontRenderParams() = default;

  ~FontRenderParams() { NOTREACHED(); }

  THREAD_CHECKER(thread_checker_);
  std::optional<gfx::FontRenderParams> params_;
  raw_ptr<GpuHostImpl> gpu_host_impl_ = nullptr;
};

FontRenderParams& GetFontRenderParams() {
  static base::NoDestructor<FontRenderParams> instance;
  return *instance;
}

#if BUILDFLAG(IS_OZONE)
bool IsHdrEnabledForGpuInfo(const gpu::GPUInfo& gpu_info) {
  return gpu_info.skia_backend_type != gpu::SkiaBackendType::kUnknown &&
         gpu_info.skia_backend_type != gpu::SkiaBackendType::kNone;
}
#endif

#if BUILDFLAG(SKIA_USE_DAWN)
std::string GraphiteDawnCacheVersion() {
  // We use a combination of Dawn and Skia's git hashes as the cache version.
  // - Dawn's git hash is because a new Dawn's version might change the way
  // shaders are compiled.
  // - Skia's git hash is because some cached shaders might not be used in a
  // newer version of Skia.
  return SKIA_COMMIT_HASH "_" DAWN_COMMIT_HASH;
}
#endif

bool CanUseShaderCache(const gpu::GpuDiskCacheHandle& handle,
                       const std::optional<bool>& gpu_uses_graphite) {
  if (handle == gpu::GpuDiskCacheHandle(gpu::kGraphiteDawnGpuDiskCacheHandle) &&
      !gpu_uses_graphite.value()) {
    // GraphiteDawn cache is not used when Skia Graphite is disabled.
    return false;
  }
  if (handle == gpu::GpuDiskCacheHandle(gpu::kGrShaderGpuDiskCacheHandle) &&
      gpu_uses_graphite.value()) {
    // GrShader cache is not used when Skia Graphite is enabled.
    return false;
  }
  return true;
}

}  // namespace

GpuHostImpl::InitParams::InitParams() = default;

GpuHostImpl::InitParams::InitParams(InitParams&&) = default;

GpuHostImpl::InitParams::~InitParams() = default;

GpuHostImpl::GpuHostImpl(Delegate* delegate,
                         mojo::PendingRemote<mojom::VizMain> viz_main,
                         InitParams params)
    : delegate_(delegate),
      viz_main_(std::move(viz_main)),
      params_(std::move(params)) {
  // Create a special GPU info collection service if the GPU process is used for
  // info collection only.
#if BUILDFLAG(IS_WIN)
  if (params_.info_collection_gpu_process) {
    viz_main_->CreateInfoCollectionGpuService(
        info_collection_gpu_service_remote_.BindNewPipeAndPassReceiver());
    return;
  }
#endif

  DCHECK(delegate_);

  mojo::PendingRemote<discardable_memory::mojom::DiscardableSharedMemoryManager>
      discardable_manager_remote;
  delegate_->BindDiscardableMemoryReceiver(
      discardable_manager_remote.InitWithNewPipeAndPassReceiver());

  scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr;
#if BUILDFLAG(IS_MAC)
  if (params_.main_thread_task_runner->BelongsToCurrentThread())
    task_runner = ui::WindowResizeHelperMac::Get()->task_runner();
#endif

#if BUILDFLAG(IS_ANDROID)
  viz_main_->SetHostProcessId(base::GetCurrentProcId());
#endif

  mojom::GpuServiceCreationParamsPtr gpu_service_params =
      mojom::GpuServiceCreationParams::New();
#if BUILDFLAG(IS_OZONE)

#if BUILDFLAG(IS_LINUX)
  // Linux has an issue when running in single-process mode wherein
  // GetPlatformRuntimeProperties() browser-side calls can have a data race with
  // in-process GPU service initialization. The call to
  // GetPlatformRuntimeProperties() below tickles that data race. Note that
  // running in single-process mode on Linux is done only in test contexts.
  const bool can_initialize_supports_overlays =
      !params_.gpu_service_running_in_process;
#else
  constexpr bool can_initialize_supports_overlays = true;
#endif

  if (can_initialize_supports_overlays) {
    gpu_service_params->supports_overlays = ui::OzonePlatform::GetInstance()
                                                ->GetPlatformRuntimeProperties()
                                                .supports_overlays;
  }
#endif

  viz_main_->CreateGpuService(
      gpu_service_remote_.BindNewPipeAndPassReceiver(task_runner),
      gpu_host_receiver_.BindNewPipeAndPassRemote(task_runner),
      gpu_logging_receiver_.BindNewPipeAndPassRemote(task_runner),
      std::move(discardable_manager_remote),
      use_shader_cache_shm_count_.CloneRegion(), std::move(gpu_service_params));
  MaybeSendFontRenderParams();

  // The persistent cache is not used by the GPU process for info collection.
  // Avoid loading the cache files multiple times.
  if (!params_.info_collection_gpu_process) {
    InitPersistentCache();
  }

#if BUILDFLAG(IS_OZONE)
  InitOzone();
#endif  // BUILDFLAG(IS_OZONE)
}

GpuHostImpl::~GpuHostImpl() {
  ClearPersistentCaches(false);
  GetFontRenderParams().SetGpuHostImpl(nullptr);
  SendOutstandingReplies();
}

void GpuHostImpl::NotifyWorkloadIncrease() {
#if BUILDFLAG(IS_ANDROID)
  viz_main_->NotifyWorkloadIncrease();
#endif
}

// static
void GpuHostImpl::InitFontRenderParams(const gfx::FontRenderParams& params) {
  DCHECK(!GetFontRenderParams().Get());
  GetFontRenderParams().Set(params);
}

void GpuHostImpl::SetProcessId(base::ProcessId pid) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(base::kNullProcessId, pid_);
  DCHECK_NE(base::kNullProcessId, pid);
  pid_ = pid;
}

void GpuHostImpl::OnProcessCrashed() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // If the GPU process crashed while compiling a shader, we may have invalid
  // cached binaries. Completely clear the shader cache to force shader binaries
  // to be re-created.
  if (use_shader_cache_shm_count_.GetCount() > 0) {
    // Clear persistent cache files
    send_persistent_cache_files_to_service_ = false;
    ClearPersistentCaches(true);

    auto* gpu_disk_cache_factory = delegate_->GetGpuDiskCacheFactory();
    for (auto& [_, cache] : client_id_to_caches_) {
      // This call will temporarily extend the lifetime of the cache (kept
      // alive in the factory), and may drop loads of cached shader binaries if
      // it takes a while to complete. As we are intentionally dropping all
      // binaries, this behavior is fine.
      gpu_disk_cache_factory->ClearByCache(
          cache, base::Time(), base::Time::Max(), base::DoNothing());
    }
  }
}

void GpuHostImpl::AddConnectionErrorHandler(base::OnceClosure handler) {
  connection_error_handlers_.push_back(std::move(handler));
}

void GpuHostImpl::BlockLiveOffscreenContexts() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  std::set<GURL> urls(urls_with_live_offscreen_contexts_.begin(),
                      urls_with_live_offscreen_contexts_.end());
  delegate_->BlockDomainsFrom3DAPIs(urls, gpu::DomainGuilt::kUnknown);
}

void GpuHostImpl::ConnectFrameSinkManager(
    mojo::PendingReceiver<mojom::FrameSinkManager> receiver,
    mojo::PendingRemote<mojom::FrameSinkManagerClient> client,
    const DebugRendererSettings& debug_renderer_settings) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  TRACE_EVENT0("gpu", "GpuHostImpl::ConnectFrameSinkManager");

  mojom::FrameSinkManagerParamsPtr params =
      mojom::FrameSinkManagerParams::New();
  params->restart_id = params_.restart_id;
  params->use_activation_deadline =
      params_.deadline_to_synchronize_surfaces.has_value();
  params->activation_deadline_in_frames =
      params_.deadline_to_synchronize_surfaces.value_or(0u);
  params->frame_sink_manager = std::move(receiver);
  params->frame_sink_manager_client = std::move(client);
  params->debug_renderer_settings = debug_renderer_settings;
  viz_main_->CreateFrameSinkManager(std::move(params));
}

void GpuHostImpl::EstablishGpuChannel(int client_id,
                                      uint64_t client_tracing_id,
                                      bool is_gpu_host,
                                      bool enable_extra_handles_validation,
                                      bool sync,
                                      mojo::ScopedMessagePipeHandle handle,
                                      EstablishChannelCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  TRACE_EVENT2("gpu", "GpuHostImpl::EstablishGpuChannel", "client_id",
               client_id, "is_gpu_host", is_gpu_host);
  DCHECK(!(is_gpu_host && enable_extra_handles_validation));

  shutdown_timeout_.Stop();

  if (gpu::IsReservedClientId(client_id)) {
    // The display-compositor/GrShaderCache in the gpu process uses these
    // special client ids.
    std::move(callback).Run(gpu::GPUInfo(), gpu::GpuFeatureInfo(),
                            gpu::SharedImageCapabilities(),
                            EstablishChannelStatus::kGpuAccessDenied);
    return;
  }

  channel_requests_[client_id] = std::move(callback);

  if (sync) {
    gpu::GPUInfo gpu_info;
    gpu::GpuFeatureInfo gpu_feature_info;
    gpu::SharedImageCapabilities shared_image_capabilities;
    bool success = false;
    {
      mojo::SyncCallRestrictions::ScopedAllowSyncCall scoped_allow;
      gpu_service_remote_->EstablishGpuChannel(
          client_id, client_tracing_id, is_gpu_host,
          enable_extra_handles_validation, std::move(handle), &success,
          &gpu_info, &gpu_feature_info, &shared_image_capabilities);
    }
    OnChannelEstablished(client_id, /*sync=*/true, /*success=*/success,
                         gpu_info, gpu_feature_info, shared_image_capabilities);
  } else {
    gpu_service_remote_->EstablishGpuChannel(
        client_id, client_tracing_id, is_gpu_host,
        enable_extra_handles_validation, std::move(handle),
        base::BindOnce(&GpuHostImpl::OnChannelEstablished,
                       weak_ptr_factory_.GetWeakPtr(), client_id, false));
  }

  // The gpu host channel uses the same cache as the compositor client.
  if (is_gpu_host &&
      !base::FeatureList::IsEnabled(features::kGpuPersistentCache)) {
    SetChannelDiskCacheHandle(client_id,
                              gpu::kDisplayCompositorGpuDiskCacheHandle);
  }
}

void GpuHostImpl::SetChannelClientPid(int client_id,
                                      base::ProcessId client_pid) {
  gpu_service_remote_->SetChannelClientPid(client_id, client_pid);
}

void GpuHostImpl::SetChannelDiskCacheHandle(
    int client_id,
    const gpu::GpuDiskCacheHandle& handle) {
  if (params_.disable_gpu_shader_disk_cache) {
    return;
  }

  if (!CanUseShaderCache(handle, gpu_uses_graphite_)) {
    return;
  }

  scoped_refptr<gpu::GpuDiskCache> cache =
      delegate_->GetGpuDiskCacheFactory()->Get(handle);
  if (!cache) {
    // Create the cache if necessary and save a reference.
    cache = delegate_->GetGpuDiskCacheFactory()->Create(
        handle,
        base::BindRepeating(&GpuHostImpl::LoadedBlob,
                            weak_ptr_factory_.GetWeakPtr()),
        base::BindOnce(&GpuHostImpl::OnDiskCacheHandleDestoyed,
                       weak_ptr_factory_.GetWeakPtr()));
    if (!cache) {
      return;
    }
  }

  client_id_to_caches_.emplace(client_id, cache);
  gpu_service_remote_->SetChannelDiskCacheHandle(client_id, handle);
}

void GpuHostImpl::RemoveChannelDiskCacheHandles(int client_id) {
  // Release the handle, then release the cache.
  auto [start, end] = client_id_to_caches_.equal_range(client_id);
  for (auto it = start; it != end; ++it) {
    delegate_->GetGpuDiskCacheFactory()->ReleaseCacheHandle(it->second.get());
  }
  client_id_to_caches_.erase(client_id);
}

void GpuHostImpl::CloseChannel(int client_id) {
  gpu_service_remote_->CloseChannel(client_id);

  channel_requests_.erase(client_id);
}

void GpuHostImpl::CancelEstablishGpuChannel(int client_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(channel_requests_.contains(client_id));
  channel_requests_.erase(client_id);
  // Track that we cancelled a request. Mojo guarantees reply order, so the
  // next reply for this client ID will be the one from the cancelled request.
  // We track it so we can drop it in `OnChannelEstablished` and avoid it
  // consuming the callback of a subsequent request.
  cancelled_channel_requests_[client_id]++;
}

#if BUILDFLAG(USE_VIZ_DEBUGGER)
void GpuHostImpl::FilterVisualDebugStream(base::DictValue json) {
  viz_main_->FilterDebugStream(std::move(json));
}

void GpuHostImpl::StartVisualDebugStream(
    base::RepeatingCallback<void(base::Value)> callback) {
  viz_debug_output_callback_ = std::move(callback);
  viz_main_->StartDebugStream(viz_debug_output_.BindNewPipeAndPassRemote());
}

void GpuHostImpl::StopVisualDebugStream() {
  viz_main_->StopDebugStream();
  viz_debug_output_.reset();
}
#endif

void GpuHostImpl::SendOutstandingReplies() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  for (auto& handler : connection_error_handlers_)
    std::move(handler).Run();
  connection_error_handlers_.clear();

  // Send empty channel handles for all EstablishChannel requests.
  for (auto& entry : channel_requests_) {
    std::move(entry.second)
        .Run(gpu::GPUInfo(), gpu::GpuFeatureInfo(),
             gpu::SharedImageCapabilities(),
             EstablishChannelStatus::kGpuHostInvalid);
  }
  channel_requests_.clear();
}

void GpuHostImpl::BindInterface(const std::string& interface_name,
                                mojo::ScopedMessagePipeHandle interface_pipe) {
  delegate_->BindInterface(interface_name, std::move(interface_pipe));
}

mojom::GpuService* GpuHostImpl::gpu_service() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(gpu_service_remote_.is_bound());
  return gpu_service_remote_.get();
}

#if BUILDFLAG(IS_WIN)
mojom::InfoCollectionGpuService* GpuHostImpl::info_collection_gpu_service() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(info_collection_gpu_service_remote_.is_bound());
  return info_collection_gpu_service_remote_.get();
}
#endif

#if BUILDFLAG(IS_OZONE)

void GpuHostImpl::InitOzone() {
  // Ozone needs to send the primary DRM device to GPU service as early as
  // possible to ensure the latter always has a valid device.
  // https://crbug.com/608839
  //
  // The Ozone/Wayland requires mojo communication to be established to be
  // functional with a separate gpu process. Thus, using the PlatformProperties,
  // check if there is such a requirement.
  auto interface_binder = base::BindRepeating(&GpuHostImpl::BindInterface,
                                              weak_ptr_factory_.GetWeakPtr());
  auto terminate_callback = base::BindOnce(&GpuHostImpl::TerminateGpuProcess,
                                           weak_ptr_factory_.GetWeakPtr());

  ui::OzonePlatform::GetInstance()
      ->GetGpuPlatformSupportHost()
      ->OnGpuServiceLaunched(params_.restart_id, interface_binder,
                             std::move(terminate_callback));
}

void GpuHostImpl::TerminateGpuProcess(const std::string& message) {
  delegate_->TerminateGpuProcess(message);
}

#endif  // BUILDFLAG(IS_OZONE)

void GpuHostImpl::InitPersistentCache() {
    auto* persistent_cache_file_factory =
        PersistentCacheSandboxedFileFactory::GetInstance();
    if (!persistent_cache_file_factory) {
      // This can happen in tests when the cache directory is not defined.
      return;
    }

    auto load_persistent_cache = [&](gpu::GpuDiskCacheHandle handle,
                                     std::string cache_version) {
      persistent_cache_file_factory->CreateFilesAsync(
          GetGpuDiskCacheSubdir(gpu::GetHandleType(handle)), cache_version,
          base::BindOnce(
              [](base::WeakPtr<GpuHostImpl> gpu_host,
                 gpu::GpuDiskCacheHandle handle,
                 std::optional<persistent_cache::PendingBackend>
                     pending_backend) {
                if (!gpu_host || !pending_backend) {
                  return;
                }

                gpu_host->OnPersistentCacheFilesCreated(
                    handle, std::move(*pending_backend));
              },
              weak_ptr_factory_.GetWeakPtr(), handle),
          /*extra_opts=*/
          {.retries = 30, .retry_delay = base::Milliseconds(100)});
    };

    if (base::FeatureList::IsEnabled(features::kGpuPersistentCache)) {
      // The compositor and Skia cache both use GrShaderGpuDiskCache with the
      // persistent cache.
      load_persistent_cache(gpu::kGrShaderGpuDiskCacheHandle, params_.product);
    }
#if BUILDFLAG(SKIA_USE_DAWN)
    if (features::SkiaGraphiteUsesPersistentCache()) {
      load_persistent_cache(gpu::kGraphiteDawnGpuDiskCacheHandle,
                            GraphiteDawnCacheVersion());
    }
#endif  // BUILDFLAG(SKIA_USE_DAWN)
}

void GpuHostImpl::SetChannelPersistentCachePendingBackend(
    int client_id,
    const gpu::GpuDiskCacheHandle& handle,
    persistent_cache::PendingBackend pending_backend) {
  TRACE_EVENT2("gpu", "GpuHostImpl::SetChannelPersistentCachePendingBackend",
               "client_id", client_id, "handle_type", GetHandleType(handle));
  if (!CanUseShaderCache(handle, gpu_uses_graphite_)) {
    if (auto* factory = PersistentCacheSandboxedFileFactory::GetInstance()) {
      factory->DeletePendingBackendAsync(std::move(pending_backend));
    }
    return;
  }
  gpu_service()->SetChannelPersistentCachePendingBackend(
      client_id, handle, std::move(pending_backend));
}

std::string GpuHostImpl::GetShaderPrefixKey() {
  if (shader_prefix_key_.empty()) {
    const gpu::GPUInfo& info = delegate_->GetGPUInfo();
    const gpu::GPUInfo::GPUDevice& active_gpu = info.active_gpu();

    shader_prefix_key_ = params_.product + "-" + info.gl_vendor + "-" +
                         info.gl_renderer + "-" + active_gpu.driver_version +
                         "-" + active_gpu.driver_vendor + "-" +
                         base::SysInfo::ProcessCPUArchitecture();

#if BUILDFLAG(IS_ANDROID)
    std::string build_fp = base::android::android_info::android_build_fp();
    shader_prefix_key_ += "-" + build_fp;
#endif
  }

  return shader_prefix_key_;
}

void GpuHostImpl::LoadedBlob(const gpu::GpuDiskCacheHandle& handle,
                             const std::string& key,
                             const std::string& data) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  TRACE_EVENT1("gpu", "GpuHostImpl::LoadedBlob", "handle_type",
               GetHandleType(handle));
  gpu_service_remote_->LoadedBlob(handle, key, data);
}

void GpuHostImpl::OnDiskCacheHandleDestoyed(
    const gpu::GpuDiskCacheHandle& handle) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  gpu_service_remote_->OnDiskCacheHandleDestoyed(handle);
}

void GpuHostImpl::OnChannelEstablished(
    int client_id,
    bool sync,
    bool success,
    const gpu::GPUInfo& gpu_info,
    const gpu::GpuFeatureInfo& gpu_feature_info,
    const gpu::SharedImageCapabilities& shared_image_capabilities) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  TRACE_EVENT0("gpu", "GpuHostImpl::OnChannelEstablished");

#if BUILDFLAG(IS_OZONE)
  ui::OzonePlatform::GetInstance()
      ->GetGpuPlatformSupportHost()
      ->OnHdrEnabledChanged(IsHdrEnabledForGpuInfo(gpu_info));
#endif  // BUILDFLAG(IS_OZONE)

  auto cancelled_it = cancelled_channel_requests_.find(client_id);
  if (cancelled_it != cancelled_channel_requests_.end()) {
    // Drop the reply from the cancelled request to prevent it from consuming
    // the callback of a subsequent request.
    cancelled_it->second--;
    if (cancelled_it->second == 0) {
      cancelled_channel_requests_.erase(cancelled_it);
    }
    return;
  }

  auto it = channel_requests_.find(client_id);
  if (it == channel_requests_.end())
    return;

  auto callback = std::move(it->second);
  channel_requests_.erase(it);

  if (!success) {
    std::move(callback).Run(gpu::GPUInfo(), gpu::GpuFeatureInfo(),
                            gpu::SharedImageCapabilities(),
                            EstablishChannelStatus::kGpuHostInvalid);
    return;
  }

  // TODO(jam): always use GPUInfo & GpuFeatureInfo from the service once we
  // know there's no issue with the ProcessHostOnUI which is the only mode
  // that currently uses it. This is because in that mode the sync mojo call
  // in the caller means we won't get the async DidInitialize() call before
  // this point, so the delegate_ methods won't have the GPU info structs yet.
  if (sync) {
    std::move(callback).Run(gpu_info, gpu_feature_info,
                            shared_image_capabilities,
                            EstablishChannelStatus::kSuccess);
  } else {
    std::move(callback).Run(
        delegate_->GetGPUInfo(), delegate_->GetGpuFeatureInfo(),
        shared_image_capabilities, EstablishChannelStatus::kSuccess);
  }
}

void GpuHostImpl::DidInitialize(
    const gpu::GPUInfo& gpu_info,
    const gpu::GpuFeatureInfo& gpu_feature_info,
    const std::optional<gpu::GPUInfo>& gpu_info_for_hardware_gpu,
    const std::optional<gpu::GpuFeatureInfo>& gpu_feature_info_for_hardware_gpu,
    const gfx::GpuExtraInfo& gpu_extra_info) {
  TRACE_EVENT0("gpu", "GpuHostImpl::DidInitialize");
  delegate_->DidInitialize(gpu_info, gpu_feature_info,
                           gpu_info_for_hardware_gpu,
                           gpu_feature_info_for_hardware_gpu, gpu_extra_info);

  gpu_uses_graphite_ =
      gpu_feature_info.status_values[gpu::GPU_FEATURE_TYPE_SKIA_GRAPHITE] ==
      gpu::kGpuFeatureStatusEnabled;

  if (!params_.disable_gpu_shader_disk_cache) {
    // Signal that any delayed loads of the persistent cache files should be
    // immediately forwarded to the GPU process.
    send_persistent_cache_files_to_service_ = true;

    // Forward existing persistent cache files now. They would only have been
    // loaded if the experiment is active.
    for (auto& persistent_cache_files : persistent_cache_files_) {
      const gpu::GpuDiskCacheHandle& handle = persistent_cache_files.first;
      persistent_cache::PendingBackend files =
          std::move(persistent_cache_files.second);
      SetChannelPersistentCachePendingBackend(gpu::GetHandleValue(handle),
                                              handle, std::move(files));
    }
    persistent_cache_files_.clear();

    if (!base::FeatureList::IsEnabled(features::kGpuPersistentCache)) {
      SetChannelDiskCacheHandle(gpu::kDisplayCompositorClientId,
                                gpu::kDisplayCompositorGpuDiskCacheHandle);
      SetChannelDiskCacheHandle(gpu::kGrShaderCacheClientId,
                                gpu::kGrShaderGpuDiskCacheHandle);
    }
    if (!features::SkiaGraphiteUsesPersistentCache()) {
      SetChannelDiskCacheHandle(gpu::kGraphiteDawnClientId,
                                gpu::kGraphiteDawnGpuDiskCacheHandle);
    }
  }
}

void GpuHostImpl::DidFailInitialize() {
  delegate_->DidFailInitialize();
}

void GpuHostImpl::DidCreateContextSuccessfully() {
  delegate_->DidCreateContextSuccessfully();
}

void GpuHostImpl::DidCreateOffscreenContext(const GURL& url) {
  urls_with_live_offscreen_contexts_.insert(url);
}

void GpuHostImpl::DidDestroyOffscreenContext(const GURL& url) {
  // We only want to remove *one* of the entries in the multiset for this
  // particular URL, so can't use the erase method taking a key.
  auto candidate = urls_with_live_offscreen_contexts_.find(url);
  if (candidate != urls_with_live_offscreen_contexts_.end())
    urls_with_live_offscreen_contexts_.erase(candidate);
}

void GpuHostImpl::DidDestroyChannel(int32_t client_id) {
  TRACE_EVENT0("gpu", "GpuHostImpl::DidDestroyChannel");
  client_id_to_caches_.erase(client_id);
}

void GpuHostImpl::DidDestroyAllChannels() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!channel_requests_.empty())
    return;
  constexpr base::TimeDelta kShutDownTimeout = base::Seconds(10);
  shutdown_timeout_.Start(FROM_HERE, kShutDownTimeout,
                          base::BindOnce(&GpuHostImpl::MaybeShutdownGpuProcess,
                                         base::Unretained(this)));
}

void GpuHostImpl::MaybeShutdownGpuProcess() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(channel_requests_.empty());
  delegate_->MaybeShutdownGpuProcess();
}

void GpuHostImpl::DidLoseContext(gpu::error::ContextLostReason reason,
                                 const GURL& active_url) {
  TRACE_EVENT2("gpu", "GpuHostImpl::DidLoseContext", "reason", reason, "url",
               active_url.possibly_invalid_spec());

  if (active_url.is_empty()) {
    return;
  }

  gpu::DomainGuilt guilt = gpu::DomainGuilt::kUnknown;
  switch (reason) {
    case gpu::error::kGuilty:
      guilt = gpu::DomainGuilt::kKnown;
      break;
    // Treat most other error codes as though they had unknown provenance.
    // In practice this doesn't affect the user experience. A lost context
    // of either known or unknown guilt still causes user-level 3D APIs
    // (e.g. WebGL) to be blocked on that domain until the user manually
    // reenables them.
    case gpu::error::kUnknown:
    case gpu::error::kOutOfMemory:
    case gpu::error::kMakeCurrentFailed:
    case gpu::error::kGpuChannelLost:
    case gpu::error::kInvalidGpuMessage:
      break;
    case gpu::error::kInnocent:
      return;
  }

  std::set<GURL> urls{active_url};
  delegate_->BlockDomainsFrom3DAPIs(urls, guilt);
}

void GpuHostImpl::DisableGpuCompositing() {
  delegate_->DisableGpuCompositing();
}

void GpuHostImpl::GetIsolationKey(
    int32_t client_id,
    const blink::WebGPUExecutionContextToken& token,
    GetIsolationKeyCallback cb) {
  std::string isolation_key = delegate_->GetIsolationKey(client_id, token);
  std::move(cb).Run(isolation_key);
}

void GpuHostImpl::DidUpdateGPUInfo(const gpu::GPUInfo& gpu_info) {
  delegate_->DidUpdateGPUInfo(gpu_info);
#if BUILDFLAG(IS_OZONE)
  ui::OzonePlatform::GetInstance()
      ->GetGpuPlatformSupportHost()
      ->OnHdrEnabledChanged(IsHdrEnabledForGpuInfo(gpu_info));
#endif  // BUILDFLAG(IS_OZONE)
}

#if BUILDFLAG(IS_WIN)
void GpuHostImpl::DidUpdateOverlayInfo(const gpu::OverlayInfo& overlay_info) {
  delegate_->DidUpdateOverlayInfo(overlay_info);
}

void GpuHostImpl::DidUpdateDXGIInfo(gfx::mojom::DXGIInfoPtr dxgi_info) {
  delegate_->DidUpdateDXGIInfo(std::move(dxgi_info));
}

void GpuHostImpl::AddChildWindow(gpu::SurfaceHandle parent_window,
                                 gpu::SurfaceHandle child_window) {
  if (pid_ != base::kNullProcessId) {
    gfx::RenderingWindowManager::GetInstance()->RegisterChild(
        parent_window, child_window, /*expected_child_process_id=*/pid_);
  }
}
#endif  // BUILDFLAG(IS_WIN)

void GpuHostImpl::MaybeSendFontRenderParams() {
  if (const auto& params = GetFontRenderParams().Get()) {
    viz_main_->SetRenderParams(params->subpixel_rendering,
                               params->text_contrast, params->text_gamma);
  } else {
    GetFontRenderParams().SetGpuHostImpl(this);
  }
}

gpu::GpuProcessHostShmCount* GpuHostImpl::GetShaderCacheShmCountForTesting() {
  return &use_shader_cache_shm_count_;
}

void GpuHostImpl::StoreBlobToDisk(const gpu::GpuDiskCacheHandle& handle,
                                  const std::string& key,
                                  const std::string& blob) {
  scoped_refptr<gpu::GpuDiskCache> cache =
      delegate_->GetGpuDiskCacheFactory()->Get(handle);
  if (!cache) {
    return;
  }

  TRACE_EVENT1("gpu", "GpuHostImpl::StoreBlobToDisk", "handle_type",
               GetHandleType(handle));
  cache->Cache(key, blob);
}

void GpuHostImpl::ClearGrShaderDiskCache() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto* gpu_disk_cache_factory = delegate_->GetGpuDiskCacheFactory();
  for (auto& [client_id, cache] : client_id_to_caches_) {
    // This call will temporarily extend the lifetime of the cache (kept
    // alive in the factory), and may drop loads of cached shader binaries if
    // it takes a while to complete. As we are intentionally dropping all
    // binaries, this behavior is fine.
    if (client_id == gpu::kGrShaderCacheClientId) {
      gpu_disk_cache_factory->ClearByCache(
          cache, base::Time(), base::Time::Max(), base::DoNothing());
    }
  }
}

#if BUILDFLAG(IS_WIN)
void GpuHostImpl::EnsureWebNNExecutionProvidersReady(
    EnsureWebNNExecutionProvidersReadyCallback cb) {
  webnn::EnsureExecutionProvidersReady(std::move(cb));
}

void GpuHostImpl::Delegate::RequestWebNNCompilerContext(
    webnn::mojom::CreateContextOptionsPtr context_options,
    const webnn::ContextProperties& context_properties,
    const webnn::EpDeviceInfo& target_device,
    mojo::PendingReceiver<webnn::mojom::WebNNCompilerContext>
        compiler_context_receiver,
    mojo::PendingRemote<webnn::mojom::WebNNModelLoader> model_loader_remote,
    RequestWebNNCompilerContextResultCallback callback) {
  std::move(callback).Run(false);
}

void GpuHostImpl::RequestWebNNCompilerContext(
    webnn::mojom::CreateContextOptionsPtr context_options,
    const webnn::ContextProperties& context_properties,
    const webnn::EpDeviceInfo& target_device,
    mojo::PendingReceiver<webnn::mojom::WebNNCompilerContext>
        compiler_context_receiver,
    mojo::PendingRemote<webnn::mojom::WebNNModelLoader> model_loader_remote,
    RequestWebNNCompilerContextCallback callback) {
  delegate_->RequestWebNNCompilerContext(
      std::move(context_options), context_properties, target_device,
      std::move(compiler_context_receiver), std::move(model_loader_remote),
      std::move(callback));
}
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_APPLE)
void GpuHostImpl::Delegate::CopyWebNNCompiledModel(
    const base::FilePath& compiler_model_path,
    CopyWebNNCompiledModelCallback callback) {
  std::move(callback).Run(std::nullopt);
}

void GpuHostImpl::CopyWebNNCompiledModel(
    const base::FilePath& compiler_model_path,
    CopyWebNNCompiledModelCallback callback) {
  delegate_->CopyWebNNCompiledModel(compiler_model_path, std::move(callback));
}
#endif

void GpuHostImpl::CreateWebNNWeightsFile(CreateWebNNWeightsFileCallback cb) {
  webnn::CreateWeightsFile(std::move(cb));
}

void GpuHostImpl::RecordLogMessage(int32_t severity,
                                   const std::string& header,
                                   const std::string& message) {
  delegate_->RecordLogMessage(severity, header, message);
}

#if BUILDFLAG(USE_VIZ_DEBUGGER)
void GpuHostImpl::LogFrame(base::Value frame_data) {
  if (!viz_debug_output_callback_.is_null())
    viz_debug_output_callback_.Run(std::move(frame_data));
}
#endif

void GpuHostImpl::ClearPersistentCaches(bool delete_cache_files) {
  auto* persistent_cache_file_factory =
      PersistentCacheSandboxedFileFactory::GetInstance();
  if (!persistent_cache_file_factory) {
    return;
  }

  auto clear_persistent_cache = [&](const gpu::GpuDiskCacheHandle& handle,
                                    const std::string& cache_version) {
    // If a pending backend was created but not sent to the service yet, tell
    // the factory to asynchronously delete it since it is a potentially
    // blocking call.
    if (auto iter = persistent_cache_files_.find(handle);
        iter != persistent_cache_files_.end()) {
      persistent_cache_file_factory->DeletePendingBackendAsync(
          std::move(iter->second));
      persistent_cache_files_.erase(iter);
    }

    // if `delete_cache_files` is set, delete all cache data.
    if (delete_cache_files) {
      persistent_cache_file_factory->ClearFilesAsync(
          GetGpuDiskCacheSubdir(gpu::GetHandleType(handle)), cache_version,
          base::DoNothing());
    }
  };

  clear_persistent_cache(gpu::kGrShaderGpuDiskCacheHandle, params_.product);
#if BUILDFLAG(SKIA_USE_DAWN)
  clear_persistent_cache(gpu::kGraphiteDawnGpuDiskCacheHandle,
                         GraphiteDawnCacheVersion());
#endif

  DCHECK(persistent_cache_files_.empty());
}

void GpuHostImpl::OnPersistentCacheFilesCreated(
    gpu::GpuDiskCacheHandle handle,
    persistent_cache::PendingBackend pending_backend) {
  TRACE_EVENT0("gpu", "GpuHostImpl::OnPersistentCacheFilesCreated");

  if (send_persistent_cache_files_to_service_) {
    // If the service is already initialized, we send the files  immediately.
    SetChannelPersistentCachePendingBackend(gpu::GetHandleValue(handle), handle,
                                            std::move(pending_backend));
  } else {
    persistent_cache_files_[handle] = std::move(pending_backend);
  }
}

}  // namespace viz
