//
// Copyright (c) 2014 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.
//

// DisplayD3D.cpp: D3D implementation of egl::Display

// These headers annoyingly have to be the first lines in the file.
#include <Windows.h>
#include <cfgmgr32.h>
#include <strsafe.h>
#include <setupapi.h>
#include <VersionHelpers.h>

#include "libANGLE/renderer/d3d/DisplayD3D.h"

#include <EGL/eglext.h>

#include "libANGLE/Context.h"
#include "libANGLE/Config.h"
#include "libANGLE/Display.h"
#include "libANGLE/Surface.h"
#include "libANGLE/histogram_macros.h"
#include "libANGLE/renderer/d3d/EGLImageD3D.h"
#include "libANGLE/renderer/d3d/RendererD3D.h"
#include "libANGLE/renderer/d3d/SurfaceD3D.h"
#include "libANGLE/renderer/d3d/SwapChainD3D.h"
#include "libANGLE/renderer/d3d/DeviceD3D.h"

#if defined (ANGLE_ENABLE_D3D9)
#   include "libANGLE/renderer/d3d/d3d9/Renderer9.h"
#endif // ANGLE_ENABLE_D3D9

#if defined (ANGLE_ENABLE_D3D11)
#   include "libANGLE/renderer/d3d/d3d11/Renderer11.h"
#endif // ANGLE_ENABLE_D3D11

#if defined (ANGLE_TEST_CONFIG)
#   define ANGLE_DEFAULT_D3D11 1
#endif

#if !defined(ANGLE_DEFAULT_D3D11)
// Enables use of the Direct3D 11 API for a default display, when available
#   define ANGLE_DEFAULT_D3D11 1
#endif

namespace rx
{

typedef RendererD3D *(*CreateRendererD3DFunction)(egl::Display*);

template <typename RendererType>
static RendererD3D *CreateTypedRendererD3D(egl::Display *display)
{
    return new RendererType(display);
}

egl::Error CreateRendererD3D(egl::Display *display, RendererD3D **outRenderer)
{
    ASSERT(outRenderer != nullptr);

    std::vector<CreateRendererD3DFunction> rendererCreationFunctions;

    if (display->getPlatform() == EGL_PLATFORM_ANGLE_ANGLE)
    {
        const auto &attribMap              = display->getAttributeMap();
        EGLNativeDisplayType nativeDisplay = display->getNativeDisplayId();

        EGLint requestedDisplayType = static_cast<EGLint>(
            attribMap.get(EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE));

#   if defined(ANGLE_ENABLE_D3D11)
        if (nativeDisplay == EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE ||
            nativeDisplay == EGL_D3D11_ONLY_DISPLAY_ANGLE ||
            requestedDisplayType == EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE)
        {
            rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer11>);
        }
#   endif

#   if defined(ANGLE_ENABLE_D3D9)
        if (nativeDisplay == EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE ||
            requestedDisplayType == EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE)
        {
            rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer9>);
        }
#   endif

        if (nativeDisplay != EGL_D3D11_ELSE_D3D9_DISPLAY_ANGLE &&
            nativeDisplay != EGL_D3D11_ONLY_DISPLAY_ANGLE &&
            requestedDisplayType == EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE)
        {
        // The default display is requested, try the D3D9 and D3D11 renderers, order them using
        // the definition of ANGLE_DEFAULT_D3D11
#       if ANGLE_DEFAULT_D3D11
#           if defined(ANGLE_ENABLE_D3D11)
            rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer11>);
#           endif
#           if defined(ANGLE_ENABLE_D3D9)
            rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer9>);
#           endif
#       else
#           if defined(ANGLE_ENABLE_D3D9)
            rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer9>);
#           endif
#           if defined(ANGLE_ENABLE_D3D11)
            rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer11>);
#           endif
#       endif
        }
    }
    else if (display->getPlatform() == EGL_PLATFORM_DEVICE_EXT)
    {
#if defined(ANGLE_ENABLE_D3D11)
        if (display->getDevice()->getType() == EGL_D3D11_DEVICE_ANGLE)
        {
            rendererCreationFunctions.push_back(CreateTypedRendererD3D<Renderer11>);
        }
#endif
    }
    else
    {
        UNIMPLEMENTED();
    }

    egl::Error result(EGL_NOT_INITIALIZED, "No available renderers.");
    for (size_t i = 0; i < rendererCreationFunctions.size(); i++)
    {
        RendererD3D *renderer = rendererCreationFunctions[i](display);
        result = renderer->initialize();

#       if defined(ANGLE_ENABLE_D3D11)
            if (renderer->getRendererClass() == RENDERER_D3D11)
            {
                ASSERT(result.getID() >= 0 && result.getID() < NUM_D3D11_INIT_ERRORS);
                ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.D3D11InitializeResult",
                                            result.getID(),
                                            NUM_D3D11_INIT_ERRORS);
            }
#       endif

#       if defined(ANGLE_ENABLE_D3D9)
            if (renderer->getRendererClass() == RENDERER_D3D9)
            {
                ASSERT(result.getID() >= 0 && result.getID() < NUM_D3D9_INIT_ERRORS);
                ANGLE_HISTOGRAM_ENUMERATION("GPU.ANGLE.D3D9InitializeResult",
                                            result.getID(),
                                            NUM_D3D9_INIT_ERRORS);
            }
#       endif

        if (!result.isError())
        {
            *outRenderer = renderer;
            break;
        }
        else
        {
            // Failed to create the renderer, try the next
            SafeDelete(renderer);
        }
    }

    return result;
}

DisplayD3D::DisplayD3D() : mRenderer(nullptr)
{
}

SurfaceImpl *DisplayD3D::createWindowSurface(const egl::SurfaceState &state,
                                             const egl::Config *configuration,
                                             EGLNativeWindowType window,
                                             const egl::AttributeMap &attribs)
{
    ASSERT(mRenderer != nullptr);
    return new WindowSurfaceD3D(state, mRenderer, mDisplay, configuration, window, attribs);
}

SurfaceImpl *DisplayD3D::createPbufferSurface(const egl::SurfaceState &state,
                                              const egl::Config *configuration,
                                              const egl::AttributeMap &attribs)
{
    ASSERT(mRenderer != nullptr);
    return new PbufferSurfaceD3D(state, mRenderer, mDisplay, configuration, nullptr, attribs);
}

SurfaceImpl *DisplayD3D::createPbufferFromClientBuffer(const egl::SurfaceState &state,
                                                       const egl::Config *configuration,
                                                       EGLClientBuffer shareHandle,
                                                       const egl::AttributeMap &attribs)
{
    ASSERT(mRenderer != nullptr);
    return new PbufferSurfaceD3D(state, mRenderer, mDisplay, configuration, shareHandle, attribs);
}

SurfaceImpl *DisplayD3D::createPixmapSurface(const egl::SurfaceState &state,
                                             const egl::Config *configuration,
                                             NativePixmapType nativePixmap,
                                             const egl::AttributeMap &attribs)
{
    UNIMPLEMENTED();
    return nullptr;
}

ImageImpl *DisplayD3D::createImage(EGLenum target,
                                   egl::ImageSibling *buffer,
                                   const egl::AttributeMap &attribs)
{
    return new EGLImageD3D(mRenderer, target, buffer, attribs);
}

egl::Error DisplayD3D::getDevice(DeviceImpl **device)
{
    return mRenderer->getEGLDevice(device);
}

ContextImpl *DisplayD3D::createContext(const gl::ContextState &state)
{
    ASSERT(mRenderer != nullptr);
    return mRenderer->createContext(state);
}

StreamProducerImpl *DisplayD3D::createStreamProducerD3DTextureNV12(
    egl::Stream::ConsumerType consumerType,
    const egl::AttributeMap &attribs)
{
    ASSERT(mRenderer != nullptr);
    return mRenderer->createStreamProducerD3DTextureNV12(consumerType, attribs);
}

egl::Error DisplayD3D::makeCurrent(egl::Surface *drawSurface, egl::Surface *readSurface, gl::Context *context)
{
    return egl::Error(EGL_SUCCESS);
}

egl::Error DisplayD3D::initialize(egl::Display *display)
{
    ASSERT(mRenderer == nullptr && display != nullptr);
    mDisplay = display;
    ANGLE_TRY(CreateRendererD3D(display, &mRenderer));
    return egl::Error(EGL_SUCCESS);
}

void DisplayD3D::terminate()
{
    SafeDelete(mRenderer);
}

egl::ConfigSet DisplayD3D::generateConfigs()
{
    ASSERT(mRenderer != nullptr);
    return mRenderer->generateConfigs();
}

bool DisplayD3D::isDeviceLost() const
{
    ASSERT(mRenderer != nullptr);
    return mRenderer->isDeviceLost();
}

bool DisplayD3D::testDeviceLost()
{
    ASSERT(mRenderer != nullptr);
    return mRenderer->testDeviceLost();
}

egl::Error DisplayD3D::restoreLostDevice()
{
    // Release surface resources to make the Reset() succeed
    for (auto &surface : mSurfaceSet)
    {
        if (surface->getBoundTexture())
        {
            surface->releaseTexImage(EGL_BACK_BUFFER);
        }
        SurfaceD3D *surfaceD3D = GetImplAs<SurfaceD3D>(surface);
        surfaceD3D->releaseSwapChain();
    }

    if (!mRenderer->resetDevice())
    {
        return egl::Error(EGL_BAD_ALLOC);
    }

    // Restore any surfaces that may have been lost
    for (const auto &surface : mSurfaceSet)
    {
        SurfaceD3D *surfaceD3D = GetImplAs<SurfaceD3D>(surface);

        egl::Error error = surfaceD3D->resetSwapChain();
        if (error.isError())
        {
            return error;
        }
    }

    return egl::Error(EGL_SUCCESS);
}

bool DisplayD3D::isValidNativeWindow(EGLNativeWindowType window) const
{
    return mRenderer->isValidNativeWindow(window);
}

void DisplayD3D::generateExtensions(egl::DisplayExtensions *outExtensions) const
{
    mRenderer->generateDisplayExtensions(outExtensions);
}

std::string DisplayD3D::getVendorString() const
{
    std::string vendorString = "Google Inc.";
    if (mRenderer)
    {
        vendorString += " " + mRenderer->getVendorString();
    }

    return vendorString;
}

void DisplayD3D::generateCaps(egl::Caps *outCaps) const
{
    // Display must be initialized to generate caps
    ASSERT(mRenderer != nullptr);

    outCaps->textureNPOT = mRenderer->getNativeExtensions().textureNPOT;
}

egl::Error DisplayD3D::waitClient() const
{
    for (auto &surface : getSurfaceSet())
    {
        SurfaceD3D *surfaceD3D = GetImplAs<SurfaceD3D>(surface);
        surfaceD3D->checkForOutOfDateSwapChain();
    }

    return egl::Error(EGL_SUCCESS);
}

egl::Error DisplayD3D::waitNative(EGLint engine,
                                  egl::Surface *drawSurface,
                                  egl::Surface *readSurface) const
{
    if (drawSurface != nullptr)
    {
        SurfaceD3D *drawSurfaceD3D = GetImplAs<SurfaceD3D>(drawSurface);
        drawSurfaceD3D->checkForOutOfDateSwapChain();
    }

    if (readSurface != nullptr)
    {
        SurfaceD3D *readurfaceD3D = GetImplAs<SurfaceD3D>(readSurface);
        readurfaceD3D->checkForOutOfDateSwapChain();
    }

    return egl::Error(EGL_SUCCESS);
}

// TODO(jmadill): Do more complete GPU info collection.
egl::Error DisplayD3D::getDriverVersion(const std::string &deviceId, std::string *resultOut)
{
#if defined(ANGLE_ENABLE_WINDOWS_STORE)
    // TODO(jmadill): Figure out what to return for Windows Store.
    *resultOut = "";
    return gl::NoError();
#else
    // Display adapter class GUID from
    // https://msdn.microsoft.com/en-us/library/windows/hardware/ff553426%28v=vs.85%29.aspx
    GUID displayClass = {
        0x4d36e968, 0xe325, 0x11ce, {0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18}};

    // create device info for the display device
    HDEVINFO deviceInfo;
    if (!IsWindowsVistaOrGreater())
    {
        // Collection of information on all adapters is much slower on XP (almost
        // 100ms), and not very useful (as it's not going to use the GPU anyway), so
        // just collect information on the current device. http://crbug.com/456178
        deviceInfo = SetupDiGetClassDevsA(nullptr, deviceId.c_str(), nullptr,
                                          DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES);
    }
    else
    {
        deviceInfo = SetupDiGetClassDevsA(&displayClass, nullptr, nullptr, DIGCF_PRESENT);
    }
    if (deviceInfo == INVALID_HANDLE_VALUE)
    {
        return egl::Error(EGL_BAD_MATCH, "Creating device info failed");
    }

    bool found = false;

    DWORD index = 0;
    SP_DEVINFO_DATA deviceInfoData;
    deviceInfoData.cbSize = sizeof(deviceInfoData);
    while (SetupDiEnumDeviceInfo(deviceInfo, index++, &deviceInfoData))
    {
        CHAR value[255];
        if (SetupDiGetDeviceRegistryPropertyA(deviceInfo, &deviceInfoData, SPDRP_DRIVER, nullptr,
                                              reinterpret_cast<PBYTE>(value), sizeof(value),
                                              nullptr))
        {
            HKEY key;
            std::string driverKey = "System\\CurrentControlSet\\Control\\Class\\";
            driverKey += value;
            LONG result =
                RegOpenKeyExA(HKEY_LOCAL_MACHINE, driverKey.c_str(), 0, KEY_QUERY_VALUE, &key);
            if (result == ERROR_SUCCESS)
            {
                DWORD dwcbData = sizeof(value);
                std::string driverVersion;
                result = RegQueryValueExA(key, "DriverVersion", nullptr, nullptr,
                                          reinterpret_cast<LPBYTE>(value), &dwcbData);
                if (result == ERROR_SUCCESS)
                    driverVersion = value;

                std::string driverDate;
                dwcbData = sizeof(value);
                result   = RegQueryValueExA(key, "DriverDate", nullptr, nullptr,
                                          reinterpret_cast<LPBYTE>(value), &dwcbData);
                if (result == ERROR_SUCCESS)
                    driverDate = value;

                std::string driverVendor;
                dwcbData = sizeof(value);
                result   = RegQueryValueExA(key, "ProviderName", nullptr, nullptr,
                                          reinterpret_cast<LPBYTE>(value), &dwcbData);
                if (result == ERROR_SUCCESS)
                    driverVendor = value;

                char newDeviceID[MAX_DEVICE_ID_LEN];
                CONFIGRET status =
                    CM_Get_Device_IDA(deviceInfoData.DevInst, newDeviceID, MAX_DEVICE_ID_LEN, 0);

                if (status == CR_SUCCESS)
                {
                    std::string id = newDeviceID;

                    if (id.compare(0, deviceId.size(), deviceId) == 0)
                    {
                        *resultOut = driverVersion;
                        found      = true;
                    }
                }

                RegCloseKey(key);
            }
        }
    }

    return found ? egl::Error(EGL_SUCCESS) : egl::Error(EGL_BAD_MATCH, "No driver version found");
#endif  // defined(ANGLE_ENABLE_WINDOWS_STORE)
}

}  // namespace rx
