//
// Copyright 2019 The ANGLE Project 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 <stdint.h>

#include "GPUInfo.h"

namespace
{

void EnumerateGPUDevice(const angle::GPUInfo::GPUDevice &device,
                        angle::GPUInfo::Enumerator *enumerator)
{
    enumerator->BeginGPUDevice();
    enumerator->AddInt("vendorId", device.vendor_id);
    enumerator->AddInt("deviceId", device.device_id);
    enumerator->AddBool("active", device.active);
    enumerator->AddString("vendorString", device.vendor_string);
    enumerator->AddString("deviceString", device.device_string);
    enumerator->AddString("driverVendor", device.driver_vendor);
    enumerator->AddString("driverVersion", device.driver_version);
    enumerator->AddString("driverDate", device.driver_date);
    enumerator->AddInt("cudaComputeCapabilityMajor", device.cuda_compute_capability_major);
    enumerator->EndGPUDevice();
}

void EnumerateVideoDecodeAcceleratorSupportedProfile(
    const angle::VideoDecodeAcceleratorSupportedProfile &profile,
    angle::GPUInfo::Enumerator *enumerator)
{
    enumerator->BeginVideoDecodeAcceleratorSupportedProfile();
    enumerator->AddInt("profile", profile.profile);
    enumerator->AddInt("maxResolutionWidth", profile.max_resolution.width());
    enumerator->AddInt("maxResolutionHeight", profile.max_resolution.height());
    enumerator->AddInt("minResolutionWidth", profile.min_resolution.width());
    enumerator->AddInt("minResolutionHeight", profile.min_resolution.height());
    enumerator->AddBool("encrypted_only", profile.encrypted_only);
    enumerator->EndVideoDecodeAcceleratorSupportedProfile();
}

void EnumerateVideoEncodeAcceleratorSupportedProfile(
    const angle::VideoEncodeAcceleratorSupportedProfile &profile,
    angle::GPUInfo::Enumerator *enumerator)
{
    enumerator->BeginVideoEncodeAcceleratorSupportedProfile();
    enumerator->AddInt("profile", profile.profile);
    enumerator->AddInt("maxResolutionWidth", profile.max_resolution.width());
    enumerator->AddInt("maxResolutionHeight", profile.max_resolution.height());
    enumerator->AddInt("maxFramerateNumerator", profile.max_framerate_numerator);
    enumerator->AddInt("maxFramerateDenominator", profile.max_framerate_denominator);
    enumerator->EndVideoEncodeAcceleratorSupportedProfile();
}

const char *ImageDecodeAcceleratorTypeToString(angle::ImageDecodeAcceleratorType type)
{
    switch (type)
    {
        case angle::ImageDecodeAcceleratorType::kJpeg:
            return "JPEG";
        case angle::ImageDecodeAcceleratorType::kUnknown:
            return "Unknown";
    }
}

const char *ImageDecodeAcceleratorSubsamplingToString(
    angle::ImageDecodeAcceleratorSubsampling subsampling)
{
    switch (subsampling)
    {
        case angle::ImageDecodeAcceleratorSubsampling::k420:
            return "4:2:0";
        case angle::ImageDecodeAcceleratorSubsampling::k422:
            return "4:2:2";
    }
}

void EnumerateImageDecodeAcceleratorSupportedProfile(
    const angle::ImageDecodeAcceleratorSupportedProfile &profile,
    angle::GPUInfo::Enumerator *enumerator)
{
    enumerator->BeginImageDecodeAcceleratorSupportedProfile();
    enumerator->AddString("imageType", ImageDecodeAcceleratorTypeToString(profile.image_type));
    enumerator->AddString("minEncodedDimensions", profile.min_encoded_dimensions.ToString());
    enumerator->AddString("maxEncodedDimensions", profile.max_encoded_dimensions.ToString());
    std::string subsamplings;
    for (size_t i = 0; i < profile.subsamplings.size(); i++)
    {
        if (i > 0)
            subsamplings += ", ";
        subsamplings += ImageDecodeAcceleratorSubsamplingToString(profile.subsamplings[i]);
    }
    enumerator->AddString("subsamplings", subsamplings);
    enumerator->EndImageDecodeAcceleratorSupportedProfile();
}

#if defined(ANGLE_PLATFORM_WINDOWS)
void EnumerateOverlayCapability(const angle::OverlayCapability &cap,
                                angle::GPUInfo::Enumerator *enumerator)
{
    std::string key_string = "overlayCap";
    key_string += OverlayFormatToString(cap.format);
    enumerator->BeginOverlayCapability();
    enumerator->AddString(key_string.c_str(), cap.is_scaling_supported ? "SCALING" : "DIRECT");
    enumerator->EndOverlayCapability();
}

void EnumerateDx12VulkanVersionInfo(const angle::Dx12VulkanVersionInfo &info,
                                    angle::GPUInfo::Enumerator *enumerator)
{
    enumerator->BeginDx12VulkanVersionInfo();
    enumerator->AddBool("supportsDx12", info.supports_dx12);
    enumerator->AddBool("supportsVulkan", info.supports_vulkan);
    enumerator->AddInt("dx12FeatureLevel", static_cast<int>(info.d3d12_feature_level));
    enumerator->AddInt("vulkanVersion", static_cast<int>(info.vulkan_version));
    enumerator->EndDx12VulkanVersionInfo();
}
#endif

}  // namespace

namespace angle
{

#if defined(ANGLE_PLATFORM_WINDOWS)
const char *OverlayFormatToString(OverlayFormat format)
{
    switch (format)
    {
        case OverlayFormat::kBGRA:
            return "BGRA";
        case OverlayFormat::kYUY2:
            return "YUY2";
        case OverlayFormat::kNV12:
            return "NV12";
    }
}

bool OverlayCapability::operator==(const OverlayCapability &other) const
{
    return format == other.format && is_scaling_supported == other.is_scaling_supported;
}
#endif

VideoDecodeAcceleratorCapabilities::VideoDecodeAcceleratorCapabilities() : flags(0) {}

VideoDecodeAcceleratorCapabilities::VideoDecodeAcceleratorCapabilities(
    const VideoDecodeAcceleratorCapabilities &other) = default;

VideoDecodeAcceleratorCapabilities::~VideoDecodeAcceleratorCapabilities() = default;

ImageDecodeAcceleratorSupportedProfile::ImageDecodeAcceleratorSupportedProfile()
    : image_type(ImageDecodeAcceleratorType::kUnknown)
{}

ImageDecodeAcceleratorSupportedProfile::ImageDecodeAcceleratorSupportedProfile(
    const ImageDecodeAcceleratorSupportedProfile &other) = default;

ImageDecodeAcceleratorSupportedProfile::ImageDecodeAcceleratorSupportedProfile(
    ImageDecodeAcceleratorSupportedProfile &&other) = default;

ImageDecodeAcceleratorSupportedProfile::~ImageDecodeAcceleratorSupportedProfile() = default;

ImageDecodeAcceleratorSupportedProfile &ImageDecodeAcceleratorSupportedProfile::operator=(
    const ImageDecodeAcceleratorSupportedProfile &other) = default;

ImageDecodeAcceleratorSupportedProfile &ImageDecodeAcceleratorSupportedProfile::operator=(
    ImageDecodeAcceleratorSupportedProfile &&other) = default;

GPUInfo::GPUDevice::GPUDevice()
    : vendor_id(0), device_id(0), active(false), cuda_compute_capability_major(0)
{}

GPUInfo::GPUDevice::GPUDevice(const GPUInfo::GPUDevice &other) = default;

GPUInfo::GPUDevice::GPUDevice(GPUInfo::GPUDevice &&other) noexcept = default;

GPUInfo::GPUDevice::~GPUDevice() noexcept = default;

GPUInfo::GPUDevice &GPUInfo::GPUDevice::operator=(const GPUInfo::GPUDevice &other) = default;

GPUInfo::GPUDevice &GPUInfo::GPUDevice::operator=(GPUInfo::GPUDevice &&other) noexcept = default;

GPUInfo::GPUInfo()
    : optimus(false),
      amd_switchable(false),
      gl_reset_notification_strategy(0),
      software_rendering(false),
      direct_rendering(true),
      sandboxed(false),
      in_process_gpu(true),
      passthrough_cmd_decoder(false),
      jpeg_decode_accelerator_supported(false),
#if defined(USE_X11)
      system_visual(0),
      rgba_visual(0),
#endif
      oop_rasterization_supported(false)
{}

GPUInfo::GPUInfo(const GPUInfo &other) = default;

GPUInfo::~GPUInfo() = default;

GPUInfo::GPUDevice &GPUInfo::active_gpu()
{
    return const_cast<GPUInfo::GPUDevice &>(const_cast<const GPUInfo &>(*this).active_gpu());
}

const GPUInfo::GPUDevice &GPUInfo::active_gpu() const
{
    if (gpu.active || secondary_gpus.empty())
        return gpu;
    for (const auto &secondary_gpu : secondary_gpus)
    {
        if (secondary_gpu.active)
            return secondary_gpu;
    }
    std::cerr << "No active GPU found, returning primary GPU.\n";
    return gpu;
}

bool GPUInfo::IsInitialized() const
{
    return gpu.vendor_id != 0 || !gl_vendor.empty();
}

void GPUInfo::EnumerateFields(Enumerator *enumerator) const
{
    struct GPUInfoKnownFields
    {
        int64_t initialization_time;
        bool optimus;
        bool amd_switchable;
        GPUDevice gpu;
        std::vector<GPUDevice> secondary_gpus;
        std::string pixel_shader_version;
        std::string vertex_shader_version;
        std::string max_msaa_samples;
        std::string machine_model_name;
        std::string machine_model_version;
        std::string gl_version_string;
        std::string gl_vendor;
        std::string gl_renderer;
        std::string gl_extensions;
        std::string gl_ws_vendor;
        std::string gl_ws_version;
        std::string gl_ws_extensions;
        uint32_t gl_reset_notification_strategy;
        bool software_rendering;
        bool direct_rendering;
        bool sandboxed;
        bool in_process_gpu;
        bool passthrough_cmd_decoder;
        bool can_support_threaded_texture_mailbox;
#if defined(ANGLE_PLATFORM_WINDOWS)
        bool direct_composition;
        bool supports_overlays;
        OverlayCapabilities overlay_capabilities;
        DxDiagNode dx_diagnostics;
        Dx12VulkanVersionInfo dx12_vulkan_version_info;
#endif

        VideoDecodeAcceleratorCapabilities video_decode_accelerator_capabilities;
        VideoEncodeAcceleratorSupportedProfiles video_encode_accelerator_supported_profiles;
        bool jpeg_decode_accelerator_supported;

        ImageDecodeAcceleratorSupportedProfiles image_decode_accelerator_supported_profiles;

#if defined(USE_X11)
        VisualID system_visual;
        VisualID rgba_visual;
#endif

        bool oop_rasterization_supported;
    };

    // If this assert fails then most likely something below needs to be updated.
    // Note that this assert is only approximate. If a new field is added to
    // GPUInfo which fits within the current padding then it will not be caught.
    static_assert(sizeof(GPUInfo) == sizeof(GPUInfoKnownFields),
                  "fields have changed in GPUInfo, GPUInfoKnownFields must be updated");

    // Required fields (according to DevTools protocol) first.
    enumerator->AddString("machineModelName", machine_model_name);
    enumerator->AddString("machineModelVersion", machine_model_version);
    EnumerateGPUDevice(gpu, enumerator);
    for (const auto &secondary_gpu : secondary_gpus)
        EnumerateGPUDevice(secondary_gpu, enumerator);

    enumerator->BeginAuxAttributes();
    enumerator->AddTimeDeltaInSeconds("initializationTime", initialization_time);
    enumerator->AddBool("optimus", optimus);
    enumerator->AddBool("amdSwitchable", amd_switchable);
    enumerator->AddString("pixelShaderVersion", pixel_shader_version);
    enumerator->AddString("vertexShaderVersion", vertex_shader_version);
    enumerator->AddString("maxMsaaSamples", max_msaa_samples);
    enumerator->AddString("glVersion", gl_version);
    enumerator->AddString("glVendor", gl_vendor);
    enumerator->AddString("glRenderer", gl_renderer);
    enumerator->AddString("glExtensions", gl_extensions);
    enumerator->AddString("glWsVendor", gl_ws_vendor);
    enumerator->AddString("glWsVersion", gl_ws_version);
    enumerator->AddString("glWsExtensions", gl_ws_extensions);
    enumerator->AddInt("glResetNotificationStrategy",
                       static_cast<int>(gl_reset_notification_strategy));
    // TODO(kbr): add performance_stats.
    enumerator->AddBool("softwareRendering", software_rendering);
    enumerator->AddBool("directRendering", direct_rendering);
    enumerator->AddBool("sandboxed", sandboxed);
    enumerator->AddBool("inProcessGpu", in_process_gpu);
    enumerator->AddBool("passthroughCmdDecoder", passthrough_cmd_decoder);
    enumerator->AddBool("canSupportThreadedTextureMailbox", can_support_threaded_texture_mailbox);
    // TODO(kbr): add dx_diagnostics on Windows.
#if defined(ANGLE_PLATFORM_WINDOWS)
    enumerator->AddBool("directComposition", direct_composition);
    enumerator->AddBool("supportsOverlays", supports_overlays);
    for (const auto &cap : overlay_capabilities)
        EnumerateOverlayCapability(cap, enumerator);
    EnumerateDx12VulkanVersionInfo(dx12_vulkan_version_info, enumerator);
#endif
    enumerator->AddInt("videoDecodeAcceleratorFlags", video_decode_accelerator_capabilities.flags);
    for (const auto &profile : video_decode_accelerator_capabilities.supported_profiles)
        EnumerateVideoDecodeAcceleratorSupportedProfile(profile, enumerator);
    for (const auto &profile : video_encode_accelerator_supported_profiles)
        EnumerateVideoEncodeAcceleratorSupportedProfile(profile, enumerator);
    enumerator->AddBool("jpegDecodeAcceleratorSupported", jpeg_decode_accelerator_supported);
    for (const auto &profile : image_decode_accelerator_supported_profiles)
        EnumerateImageDecodeAcceleratorSupportedProfile(profile, enumerator);
#if defined(USE_X11)
    enumerator->AddInt64("systemVisual", system_visual);
    enumerator->AddInt64("rgbaVisual", rgba_visual);
#endif
    enumerator->AddBool("oopRasterizationSupported", oop_rasterization_supported);
    enumerator->EndAuxAttributes();
}

}  // namespace angle
