// Copyright 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "host-common/opengl/gpuinfo.h"

#include "aemu/base/ArraySize.h"
#include "aemu/base/system/System.h"
#include "aemu/base/threads/FunctorThread.h"
#include "host-common/opengl/NativeGpuInfo.h"

#include <assert.h>
#include <inttypes.h>
#include <sstream>
#include <utility>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

using android::base::arraySize;
using android::base::FunctorThread;

// Try to switch to NVIDIA on Optimus systems,
// and AMD GPU on AmdPowerXpress.
// See http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf
// and https://community.amd.com/thread/169965
// These variables need to be visible from the final emulator executable
// as exported symbols.
#ifdef _WIN32
#define FLAG_EXPORT __declspec(dllexport)
#else
#define FLAG_EXPORT __attribute__ ((visibility ("default")))
#endif

extern "C" {

FLAG_EXPORT int NvOptimusEnablement = 0x00000001;
FLAG_EXPORT int AmdPowerXpressRequestHighPerformance = 0x00000001;

}  // extern "C"

#undef FLAG_EXPORT


void GpuInfo::addDll(std::string dll_str) {
    dlls.push_back(std::move(dll_str));
}

void GpuInfoList::addGpu() {
    infos.push_back(GpuInfo());
}
GpuInfo& GpuInfoList::currGpu() {
    if (infos.empty()) { addGpu(); }
    return infos.back();
}

std::string GpuInfoList::dump() const {
    std::stringstream ss;
    for (unsigned int i = 0; i < infos.size(); i++) {
        ss << "GPU #" << i + 1 << std::endl;

        if (!infos[i].make.empty()) {
            ss << "  Make: " << infos[i].make << std::endl;
        }
        if (!infos[i].model.empty()) {
            ss << "  Model: " << infos[i].model << std::endl;
        }
        if (!infos[i].device_id.empty()) {
            ss << "  Device ID: " << infos[i].device_id << std::endl;
        }
        if (!infos[i].revision_id.empty()) {
            ss << "  Revision ID: " << infos[i].revision_id << std::endl;
        }
        if (!infos[i].version.empty()) {
            ss << "  Driver version: " << infos[i].version << std::endl;
        }
        if (!infos[i].renderer.empty()) {
            ss << "  Renderer: " << infos[i].renderer << std::endl;
        }
    }
    return ss.str();
}

void GpuInfoList::clear() {
    blacklist_status = false;
    Anglelist_status = false;
    SyncBlacklist_status = false;
    VulkanBlacklist_status = false;
    infos.clear();
}

static bool gpuinfo_query_list(GpuInfoList* gpulist,
                             const BlacklistEntry* list,
                             int size) {
    for (auto gpuinfo : gpulist->infos) {
        for (int i = 0; i < size; i++) {
            auto bl_entry = list[i];
            const char* bl_make = bl_entry.make;
            const char* bl_model = bl_entry.model;
            const char* bl_device_id = bl_entry.device_id;
            const char* bl_revision_id = bl_entry.revision_id;
            const char* bl_version = bl_entry.version;
            const char* bl_renderer = bl_entry.renderer;
            const char* bl_os = bl_entry.os;

            if (bl_make && (gpuinfo.make != bl_make))
                continue;
            if (bl_model && (gpuinfo.model != bl_model))
                continue;
            if (bl_device_id && (gpuinfo.device_id != bl_device_id))
                continue;
            if (bl_revision_id && (gpuinfo.revision_id != bl_revision_id))
                continue;
            if (bl_version && (gpuinfo.revision_id != bl_version))
                continue;
            if (bl_renderer && (gpuinfo.renderer.find(bl_renderer) ==
                                std::string::npos))
                continue;
            if (bl_os && (gpuinfo.os != bl_os))
                continue;
            return true;
        }
    }
    return false;
}

// Actual blacklist starts here.
// Most entries imported from Chrome blacklist.
static const BlacklistEntry sGpuBlacklist[] = {

        // Make | Model | DeviceID | RevisionID | DriverVersion | Renderer |
        // OS
        {nullptr, nullptr, "0x7249", nullptr, nullptr,
         nullptr, "M"},  // ATI Radeon X1900 on Mac
        {"8086", nullptr, nullptr, nullptr, nullptr,
         "Mesa", "L"},  // Linux, Intel, Mesa
        {"8086", nullptr, nullptr, nullptr, nullptr,
         "mesa", "L"},  // Linux, Intel, Mesa

        {"8086", nullptr, "27ae", nullptr, nullptr,
         nullptr, nullptr},  // Intel 945 Chipset
        {"1002", nullptr, nullptr, nullptr, nullptr, nullptr,
          "L"},  // Linux, ATI

        {nullptr, nullptr, "0x9583", nullptr, nullptr,
         nullptr, "M"},  // ATI Radeon HD2600 on Mac
        {nullptr, nullptr, "0x94c8", nullptr, nullptr,
         nullptr, "M"},  // ATI Radeon HD2400 on Mac

        {"NVIDIA (0x10de)", nullptr, "0x0324", nullptr, nullptr,
         nullptr, "M"},  // NVIDIA GeForce FX Go5200 (Mac)
        {"10DE", "NVIDIA GeForce FX Go5200", nullptr, nullptr, nullptr,
         nullptr, "W"},  // NVIDIA GeForce FX Go5200 (Win)
        {"10de", nullptr, "0324", nullptr, nullptr,
         nullptr, "L"},  // NVIDIA GeForce FX Go5200 (Linux)

        {"10de", nullptr, "029e", nullptr, nullptr,
         nullptr, "L"},  // NVIDIA Quadro FX 1500 (Linux)

        // Various Quadro FX cards on Linux
        {"10de", nullptr, "00cd", nullptr, "195.36.24",
          nullptr, "L"},
        {"10de", nullptr, "00ce", nullptr, "195.36.24",
         nullptr, "L"},
        // Driver version 260.19.6 on Linux
        {"10de", nullptr, nullptr, nullptr, "260.19.6",
         nullptr, "L"},

        {"NVIDIA (0x10de)", nullptr, "0x0393", nullptr, nullptr,
         nullptr, "M"},  // NVIDIA GeForce 7300 GT (Mac)

        // GPUs with < OpenGL 2.1 support

        // Intel Auburn
        {"8086", nullptr, "7800", nullptr, nullptr, nullptr, nullptr},

        // Intel Portola
        {"8086", nullptr, "1240", nullptr, nullptr, nullptr, nullptr},

        // Intel Whitney
        {"8086", nullptr, "7121", nullptr, nullptr, nullptr, nullptr},
        {"8086", nullptr, "7123", nullptr, nullptr, nullptr, nullptr},
        {"8086", nullptr, "7125", nullptr, nullptr, nullptr, nullptr},

        // Intel Solano
        {"8086", nullptr, "1132", nullptr, nullptr, nullptr, nullptr},

        // Intel Brookdale
        {"8086", nullptr, "2562", nullptr, nullptr, nullptr, nullptr},

        // Intel Almador
        {"8086", nullptr, "3577", nullptr, nullptr, nullptr, nullptr},

        // Intel Springdale
        {"8086", nullptr, "2572", nullptr, nullptr, nullptr, nullptr},

        // Intel Montara
        {"8086", nullptr, "3582", nullptr, nullptr, nullptr, nullptr},
        {"8086", nullptr, "358e", nullptr, nullptr, nullptr, nullptr},

        // Intel Grantsdale (2.1 desktop / ES 2.0 on Linux supported)
        {"8086", nullptr, "2582", nullptr, nullptr, nullptr, "W"},
        {"8086", nullptr, "258a", nullptr, nullptr, nullptr, "W"},

        // Intel Alviso
        {"8086", nullptr, "2592", nullptr, nullptr, nullptr, "W"},

        // Intel Lakeport
        {"8086", nullptr, "2772", nullptr, nullptr, nullptr, "W"},

        // Intel Calistoga
        {"8086", nullptr, "27a2", nullptr, nullptr, nullptr, "W"},
        {"8086", nullptr, "27ae", nullptr, nullptr, nullptr, "W"},

        // Intel Bearlake
        {"8086", nullptr, "29c2", nullptr, nullptr, nullptr, "W"},
        {"8086", nullptr, "29b2", nullptr, nullptr, nullptr, "W"},
        {"8086", nullptr, "29d2", nullptr, nullptr, nullptr, "W"},

        // Intel Pineview
        {"8086", nullptr, "a001", nullptr, nullptr, nullptr, "W"},
        {"8086", nullptr, "a011", nullptr, nullptr, nullptr, "W"},

        // Intel Lakeport
        {"8086", nullptr, "2972", nullptr, nullptr, nullptr, "W"},

        // Intel Broadwater
        {"8086", nullptr, "2992", nullptr, nullptr, nullptr, "W"},
        {"8086", nullptr, "29a2", nullptr, nullptr, nullptr, "W"},

        // Intel Bearlake 2
        {"8086", nullptr, "2982", nullptr, nullptr, nullptr, "W"},

        // Intel Crestine
        {"8086", nullptr, "2a02", nullptr, nullptr, nullptr, "W"},
        {"8086", nullptr, "2a12", nullptr, nullptr, nullptr, "W"},

        // Display-only devices

        // Microsoft Basic Render Driver
        {"1414", nullptr, "008c", nullptr, nullptr, nullptr, nullptr},

        // Matrix MGA G200eW
        {"102b", nullptr, "0532", nullptr, nullptr, nullptr, nullptr},

        // VMWare SVGA 3D
        {"15ad", nullptr, "0405", nullptr, nullptr, nullptr, nullptr},

        // Citrix Display Only Adapter
        {"5853", nullptr, "1002", nullptr, nullptr, nullptr, nullptr},

        // SeaBIOS Display-Only Device?
        {"1b36", nullptr, "0100", nullptr, nullptr, nullptr, nullptr},

        // Devices incapable of reliably calling wglMakeCurrent
        // when switching HDC

        // Intel Eaglelake
        {"8086", nullptr, "2e42", nullptr, nullptr, nullptr, "W"},
        {"8086", nullptr, "2e92", nullptr, nullptr, nullptr, "W"},
        {"8086", nullptr, "2e12", nullptr, nullptr, nullptr, "W"},
        {"8086", nullptr, "2e32", nullptr, nullptr, nullptr, "W"},
        {"8086", nullptr, "2e22", nullptr, nullptr, nullptr, "W"},

        // Intel Cantiga
        {"8086", nullptr, "2a42", nullptr, nullptr, nullptr, "W"},

        // Intel Ironlake
        {"8086", nullptr, "0042", nullptr, nullptr, nullptr, "W"},
        {"8086", nullptr, "0046", nullptr, nullptr, nullptr, "W"},
};

// If any blacklist entry matches any gpu, return true.
bool gpuinfo_query_blacklist(GpuInfoList* gpulist,
                             const BlacklistEntry* list,
                             int size) {
    return gpuinfo_query_list(gpulist, list, size);
}

#ifdef _WIN32
static const WhitelistEntry sAngleWhitelist[] = {
        // Make | Model | DeviceID | RevisionID | DriverVersion | Renderer |
        // OS
        // HD 3000 on Windows
        {"8086", nullptr, "0116", nullptr, nullptr,
         nullptr, "W"},
        {"8086", nullptr, "0126", nullptr, nullptr,
         nullptr, "W"},
        {"8086", nullptr, "0102", nullptr, nullptr,
         nullptr, "W"},
};

static bool gpuinfo_query_whitelist(GpuInfoList *gpulist,
                             const WhitelistEntry *list,
                             int size) {
    return gpuinfo_query_list(gpulist, list, size);
}

#endif

static const BlacklistEntry sSyncBlacklist[] = {
    // Make | Model | DeviceID | RevisionID | DriverVersion | Renderer |
    // OS
    // All NVIDIA Quadro NVS and NVIDIA NVS GPUs on Windows
    {"10de", nullptr, "06fd", nullptr, nullptr, nullptr, "W"}, // NVS 295
    {"10de", nullptr, "0a6a", nullptr, nullptr, nullptr, "W"}, // NVS 2100M
    {"10de", nullptr, "0a6c", nullptr, nullptr, nullptr, "W"}, // NVS 5100M
    {"10de", nullptr, "0ffd", nullptr, nullptr, nullptr, "W"}, // NVS 510
    {"10de", nullptr, "1056", nullptr, nullptr, nullptr, "W"}, // NVS 4200M
    {"10de", nullptr, "10d8", nullptr, nullptr, nullptr, "W"}, // NVS 300
    {"10de", nullptr, "014a", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 440
    {"10de", nullptr, "0165", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 285
    {"10de", nullptr, "017a", nullptr, nullptr, nullptr, "W"}, // Quadro NVS (generic)
    {"10de", nullptr, "018a", nullptr, nullptr, nullptr, "W"}, // Quadro NVS AGP8X (generic)
    {"10de", nullptr, "018c", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 50 PCI (generic)
    {"10de", nullptr, "01db", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 120M
    {"10de", nullptr, "0245", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 210S / NVIDIA GeForce 6150LE
    {"10de", nullptr, "032a", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 55/280 PCI
    {"10de", nullptr, "040c", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 570M / Mobile Quadro FX/NVS video card
    {"10de", nullptr, "0429", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 135M or Quadro NVS 140M
    {"10de", nullptr, "042b", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 135M
    {"10de", nullptr, "042f", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 290
    {"10de", nullptr, "06ea", nullptr, nullptr, nullptr, "W"}, // quadro nvs 150m
    {"10de", nullptr, "06eb", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 160M
    {"10de", nullptr, "06f8", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 420
    {"10de", nullptr, "06fa", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 450
    {"10de", nullptr, "0a2c", nullptr, nullptr, nullptr, "W"}, // Quadro NVS 5100M
};

namespace {

// A set of global variables for GPU information - a single instance of
// GpuInfoList and its loading thread.
class Globals {
    DISALLOW_COPY_AND_ASSIGN(Globals);

public:
    Globals()
        : mAsyncLoadThread([this]() {
              getGpuInfoListNative(&mGpuInfoList);

              mGpuInfoList.blacklist_status = gpuinfo_query_blacklist(
                      &mGpuInfoList, sGpuBlacklist, arraySize(sGpuBlacklist));
#ifdef _WIN32
              mGpuInfoList.Anglelist_status =
                      gpuinfo_query_whitelist(&mGpuInfoList, sAngleWhitelist,
                                              arraySize(sAngleWhitelist));
#else
              mGpuInfoList.Anglelist_status = false;
#endif
              mGpuInfoList.SyncBlacklist_status = gpuinfo_query_blacklist(
                      &mGpuInfoList, sSyncBlacklist, arraySize(sSyncBlacklist));

              mGpuInfoList.VulkanBlacklist_status =
                !isVulkanSafeToUseNative();

          }) {
        mAsyncLoadThread.start();
    }

    const GpuInfoList& gpuInfoList() {
        mAsyncLoadThread.wait();
        return mGpuInfoList;
    }

private:
    GpuInfoList mGpuInfoList;

    // Separate thread for GPU info querying:
    //
    // GPU information querying may take a while, but the implementation is
    // expected to abort if it's over reasonable limits. That's why we use a
    // separate thread that's started as soon as possible at startup and then
    // wait for it synchronoulsy before using the information.
    //
    FunctorThread mAsyncLoadThread;
};

}  // namespace

static Globals* sGlobals() {
    static Globals* g = new Globals;
    return g;
}

void async_query_host_gpu_start() {
    sGlobals();
}

const GpuInfoList& globalGpuInfoList() {
    return sGlobals()->gpuInfoList();
}

bool async_query_host_gpu_blacklisted() {
    return globalGpuInfoList().blacklist_status;
}

bool async_query_host_gpu_AngleWhitelisted() {
    return globalGpuInfoList().Anglelist_status;
}

bool async_query_host_gpu_SyncBlacklisted() {
    return globalGpuInfoList().SyncBlacklist_status;
}

bool async_query_host_gpu_VulkanBlacklisted() {
    return globalGpuInfoList().VulkanBlacklist_status;
}

void setGpuBlacklistStatus(bool switchedToSoftware) {
    // We know what we're doing here: GPU list doesn't change after it's fully
    // loaded.
    const_cast<GpuInfoList&>(globalGpuInfoList()).blacklist_status =
            switchedToSoftware;
}
