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

#include "media/gpu/gpu_video_decode_accelerator_factory.h"

#include <memory>

#include "base/memory/ptr_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "gpu/config/gpu_preferences.h"
#include "media/base/media_switches.h"
#include "media/gpu/buildflags.h"
#include "media/gpu/gpu_video_accelerator_util.h"
#include "media/gpu/macros.h"
#include "media/gpu/media_gpu_export.h"
#include "media/media_buildflags.h"

#if defined(OS_WIN)
#include "base/win/windows_version.h"
#include "media/gpu/windows/dxva_video_decode_accelerator_win.h"
#endif
#if defined(OS_MAC)
#include "media/gpu/mac/vt_video_decode_accelerator_mac.h"
#endif
#if BUILDFLAG(USE_V4L2_CODEC)
#include "media/gpu/v4l2/v4l2_device.h"
#include "media/gpu/v4l2/v4l2_slice_video_decode_accelerator.h"
#include "media/gpu/v4l2/v4l2_video_decode_accelerator.h"
#include "ui/gl/gl_surface_egl.h"
#endif
#if BUILDFLAG(USE_VAAPI)
#include "media/gpu/vaapi/vaapi_video_decode_accelerator.h"
#include "ui/gl/gl_implementation.h"
#endif

namespace media {

namespace {

gpu::VideoDecodeAcceleratorCapabilities GetDecoderCapabilitiesInternal(
    const gpu::GpuPreferences& gpu_preferences,
    const gpu::GpuDriverBugWorkarounds& workarounds) {
  if (gpu_preferences.disable_accelerated_video_decode)
    return gpu::VideoDecodeAcceleratorCapabilities();

  // Query VDAs for their capabilities and construct a set of supported
  // profiles for current platform. This must be done in the same order as in
  // CreateVDA(), as we currently preserve additional capabilities (such as
  // resolutions supported) only for the first VDA supporting the given codec
  // profile (instead of calculating a superset).
  // TODO(posciak,henryhsu): improve this so that we choose a superset of
  // resolutions and other supported profile parameters.
  VideoDecodeAccelerator::Capabilities capabilities;
#if defined(OS_WIN)
  capabilities.supported_profiles =
      DXVAVideoDecodeAccelerator::GetSupportedProfiles(gpu_preferences,
                                                       workarounds);
#elif BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION)
  VideoDecodeAccelerator::SupportedProfiles vda_profiles;
#if BUILDFLAG(USE_V4L2_CODEC)
  vda_profiles = V4L2VideoDecodeAccelerator::GetSupportedProfiles();
  GpuVideoAcceleratorUtil::InsertUniqueDecodeProfiles(
      vda_profiles, &capabilities.supported_profiles);
  vda_profiles = V4L2SliceVideoDecodeAccelerator::GetSupportedProfiles();
  GpuVideoAcceleratorUtil::InsertUniqueDecodeProfiles(
      vda_profiles, &capabilities.supported_profiles);
#endif
#if BUILDFLAG(USE_VAAPI)
  vda_profiles = VaapiVideoDecodeAccelerator::GetSupportedProfiles(workarounds);
  GpuVideoAcceleratorUtil::InsertUniqueDecodeProfiles(
      vda_profiles, &capabilities.supported_profiles);
#endif
#elif defined(OS_MAC)
  capabilities.supported_profiles =
      VTVideoDecodeAccelerator::GetSupportedProfiles();
#endif

  return GpuVideoAcceleratorUtil::ConvertMediaToGpuDecodeCapabilities(
      capabilities);
}

}  // namespace

// static
MEDIA_GPU_EXPORT std::unique_ptr<GpuVideoDecodeAcceleratorFactory>
GpuVideoDecodeAcceleratorFactory::Create(
    const GpuVideoDecodeGLClient& gl_client) {
  return base::WrapUnique(new GpuVideoDecodeAcceleratorFactory(gl_client));
}

// static
MEDIA_GPU_EXPORT gpu::VideoDecodeAcceleratorCapabilities
GpuVideoDecodeAcceleratorFactory::GetDecoderCapabilities(
    const gpu::GpuPreferences& gpu_preferences,
    const gpu::GpuDriverBugWorkarounds& workarounds) {
  // Cache the capabilities so that they will not be computed more than once per
  // GPU process. It is assumed that |gpu_preferences| and |workarounds| do not
  // change between calls.
  // TODO(sandersd): Move cache to GpuMojoMediaClient once
  // |video_decode_accelerator_capabilities| is removed from GPUInfo.
  static gpu::VideoDecodeAcceleratorCapabilities capabilities =
      GetDecoderCapabilitiesInternal(gpu_preferences, workarounds);

#if BUILDFLAG(USE_V4L2_CODEC)
  // V4L2-only: the decoder devices may not be visible at the time the GPU
  // process is starting. If the capabilities vector is empty, try to query the
  // devices again in the hope that they will have appeared in the meantime.
  // TODO(crbug.com/948147): trigger query when an device add/remove event
  // (e.g. via udev) has happened instead.
  if (capabilities.supported_profiles.empty()) {
    VLOGF(1) << "Capabilities empty, querying again...";
    capabilities = GetDecoderCapabilitiesInternal(gpu_preferences, workarounds);
  }
#endif

  return capabilities;
}

MEDIA_GPU_EXPORT std::unique_ptr<VideoDecodeAccelerator>
GpuVideoDecodeAcceleratorFactory::CreateVDA(
    VideoDecodeAccelerator::Client* client,
    const VideoDecodeAccelerator::Config& config,
    const gpu::GpuDriverBugWorkarounds& workarounds,
    const gpu::GpuPreferences& gpu_preferences,
    MediaLog* media_log) {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (gpu_preferences.disable_accelerated_video_decode)
    return nullptr;

  // Array of Create..VDA() function pointers, potentially usable on current
  // platform. This list is ordered by priority, from most to least preferred,
  // if applicable. This list must be in the same order as the querying order
  // in GetDecoderCapabilities() above.
  using CreateVDAFp = std::unique_ptr<VideoDecodeAccelerator> (
      GpuVideoDecodeAcceleratorFactory::*)(const gpu::GpuDriverBugWorkarounds&,
                                           const gpu::GpuPreferences&,
                                           MediaLog* media_log) const;
  const CreateVDAFp create_vda_fps[] = {
#if defined(OS_WIN)
    &GpuVideoDecodeAcceleratorFactory::CreateDXVAVDA,
#endif
#if BUILDFLAG(USE_VAAPI)
    &GpuVideoDecodeAcceleratorFactory::CreateVaapiVDA,
#endif
#if BUILDFLAG(USE_V4L2_CODEC)
    &GpuVideoDecodeAcceleratorFactory::CreateV4L2VDA,
    &GpuVideoDecodeAcceleratorFactory::CreateV4L2SVDA,
#endif
#if defined(OS_MAC)
    &GpuVideoDecodeAcceleratorFactory::CreateVTVDA,
#endif
  };

  std::unique_ptr<VideoDecodeAccelerator> vda;

  for (const auto& create_vda_function : create_vda_fps) {
    vda = (this->*create_vda_function)(workarounds, gpu_preferences, media_log);
    if (vda && vda->Initialize(config, client))
      return vda;
  }

  return nullptr;
}

#if defined(OS_WIN)
std::unique_ptr<VideoDecodeAccelerator>
GpuVideoDecodeAcceleratorFactory::CreateDXVAVDA(
    const gpu::GpuDriverBugWorkarounds& workarounds,
    const gpu::GpuPreferences& gpu_preferences,
    MediaLog* media_log) const {
  std::unique_ptr<VideoDecodeAccelerator> decoder;
  DVLOG(0) << "Initializing DXVA HW decoder for windows.";
  decoder.reset(new DXVAVideoDecodeAccelerator(
      gl_client_.get_context, gl_client_.make_context_current,
      gl_client_.bind_image, workarounds, gpu_preferences, media_log));
  return decoder;
}
#endif

#if BUILDFLAG(USE_V4L2_CODEC)
std::unique_ptr<VideoDecodeAccelerator>
GpuVideoDecodeAcceleratorFactory::CreateV4L2VDA(
    const gpu::GpuDriverBugWorkarounds& workarounds,
    const gpu::GpuPreferences& gpu_preferences,
    MediaLog* media_log) const {
  std::unique_ptr<VideoDecodeAccelerator> decoder;
  scoped_refptr<V4L2Device> device = V4L2Device::Create();
  if (device.get()) {
    decoder.reset(new V4L2VideoDecodeAccelerator(
        gl::GLSurfaceEGL::GetHardwareDisplay(), gl_client_.get_context,
        gl_client_.make_context_current, device));
  }
  return decoder;
}

std::unique_ptr<VideoDecodeAccelerator>
GpuVideoDecodeAcceleratorFactory::CreateV4L2SVDA(
    const gpu::GpuDriverBugWorkarounds& workarounds,
    const gpu::GpuPreferences& gpu_preferences,
    MediaLog* media_log) const {
  std::unique_ptr<VideoDecodeAccelerator> decoder;
  scoped_refptr<V4L2Device> device = V4L2Device::Create();
  if (device.get()) {
    decoder.reset(new V4L2SliceVideoDecodeAccelerator(
        device, gl::GLSurfaceEGL::GetHardwareDisplay(), gl_client_.bind_image,
        gl_client_.make_context_current));
  }
  return decoder;
}
#endif

#if BUILDFLAG(USE_VAAPI)
std::unique_ptr<VideoDecodeAccelerator>
GpuVideoDecodeAcceleratorFactory::CreateVaapiVDA(
    const gpu::GpuDriverBugWorkarounds& workarounds,
    const gpu::GpuPreferences& gpu_preferences,
    MediaLog* media_log) const {
  std::unique_ptr<VideoDecodeAccelerator> decoder;
  decoder.reset(new VaapiVideoDecodeAccelerator(gl_client_.make_context_current,
                                                gl_client_.bind_image));
  return decoder;
}
#endif

#if defined(OS_MAC)
std::unique_ptr<VideoDecodeAccelerator>
GpuVideoDecodeAcceleratorFactory::CreateVTVDA(
    const gpu::GpuDriverBugWorkarounds& workarounds,
    const gpu::GpuPreferences& gpu_preferences,
    MediaLog* media_log) const {
  std::unique_ptr<VideoDecodeAccelerator> decoder;
  decoder.reset(new VTVideoDecodeAccelerator(gl_client_, media_log));
  return decoder;
}
#endif

GpuVideoDecodeAcceleratorFactory::GpuVideoDecodeAcceleratorFactory(
    const GpuVideoDecodeGLClient& gl_client)
    : gl_client_(gl_client) {}
GpuVideoDecodeAcceleratorFactory::~GpuVideoDecodeAcceleratorFactory() = default;

}  // namespace media
