//
// 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

#include "libANGLE/renderer/d3d/DisplayD3D.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/RendererD3D.h"
#include "libANGLE/renderer/d3d/SurfaceD3D.h"
#include "libANGLE/renderer/d3d/SwapChainD3D.h"
#include "libANGLE/renderer/d3d/DeviceD3D.h"

#include <EGL/eglext.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 0
#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;

    const auto &attribMap = display->getAttributeMap();
    EGLNativeDisplayType nativeDisplay = display->getNativeDisplayId();

    EGLint requestedDisplayType = 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
    }

    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;
}

#if !defined(ANGLE_ENABLE_WINDOWS_STORE)
namespace
{

LRESULT CALLBACK IntermediateWindowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
      case WM_ERASEBKGND:
        // Prevent windows from erasing the background.
        return 1;
      case WM_PAINT:
        // Do not paint anything.
        PAINTSTRUCT paint;
        if (BeginPaint(window, &paint))
        {
            EndPaint(window, &paint);
        }
        return 0;
    }

    return DefWindowProc(window, message, wParam, lParam);
}

}
#endif

DisplayD3D::DisplayD3D()
    : mDisplay(nullptr),
      mRenderer(nullptr),
      mChildWindowClass(0),
      mDevice(nullptr)
{
}

egl::Error DisplayD3D::createWindowSurface(const egl::Config *configuration, EGLNativeWindowType window,
                                           const egl::AttributeMap &attribs, SurfaceImpl **outSurface)
{
    ASSERT(mRenderer != nullptr);

    EGLint width = attribs.get(EGL_WIDTH, 0);
    EGLint height = attribs.get(EGL_HEIGHT, 0);
    EGLint fixedSize = attribs.get(EGL_FIXED_SIZE_ANGLE, EGL_FALSE);

    if (!fixedSize)
    {
        width = -1;
        height = -1;
    }

    SurfaceD3D *surface = SurfaceD3D::createFromWindow(mRenderer, mDisplay, configuration, window, fixedSize,
                                                       width, height);
    egl::Error error = surface->initialize();
    if (error.isError())
    {
        SafeDelete(surface);
        return error;
    }

    *outSurface = surface;
    return egl::Error(EGL_SUCCESS);
}

egl::Error DisplayD3D::createPbufferSurface(const egl::Config *configuration, const egl::AttributeMap &attribs,
                                            SurfaceImpl **outSurface)
{
    ASSERT(mRenderer != nullptr);

    EGLint width = attribs.get(EGL_WIDTH, 0);
    EGLint height = attribs.get(EGL_HEIGHT, 0);

    SurfaceD3D *surface = SurfaceD3D::createOffscreen(mRenderer, mDisplay, configuration, NULL, width, height);
    egl::Error error = surface->initialize();
    if (error.isError())
    {
        SafeDelete(surface);
        return error;
    }

    *outSurface = surface;
    return egl::Error(EGL_SUCCESS);
}

egl::Error DisplayD3D::createPbufferFromClientBuffer(const egl::Config *configuration, EGLClientBuffer shareHandle,
                                                     const egl::AttributeMap &attribs, SurfaceImpl **outSurface)
{
    ASSERT(mRenderer != nullptr);

    EGLint width = attribs.get(EGL_WIDTH, 0);
    EGLint height = attribs.get(EGL_HEIGHT, 0);

    SurfaceD3D *surface = SurfaceD3D::createOffscreen(mRenderer, mDisplay, configuration, shareHandle,
                                                      width, height);
    egl::Error error = surface->initialize();
    if (error.isError())
    {
        SafeDelete(surface);
        return error;
    }

    *outSurface = surface;
    return egl::Error(EGL_SUCCESS);
}

egl::Error DisplayD3D::createPixmapSurface(const egl::Config *configuration, NativePixmapType nativePixmap,
                                           const egl::AttributeMap &attribs, SurfaceImpl **outSurface)
{
    ASSERT(mRenderer != nullptr);

    UNIMPLEMENTED();
    *outSurface = nullptr;
    return egl::Error(EGL_BAD_DISPLAY);
}

egl::Error DisplayD3D::getDevice(DeviceImpl **device)
{
    *device = reinterpret_cast<DeviceImpl*>(mDevice);
    ASSERT(*device != nullptr);
    return egl::Error(EGL_SUCCESS);
}

egl::Error DisplayD3D::createContext(const egl::Config *config, const gl::Context *shareContext, const egl::AttributeMap &attribs,
                                     gl::Context **outContext)
{
    ASSERT(mRenderer != nullptr);

    EGLint clientVersion = attribs.get(EGL_CONTEXT_CLIENT_VERSION, 1);
    bool notifyResets = (attribs.get(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, EGL_NO_RESET_NOTIFICATION_EXT) == EGL_LOSE_CONTEXT_ON_RESET_EXT);
    bool robustAccess = (attribs.get(EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, EGL_FALSE) == EGL_TRUE);

    *outContext = new gl::Context(config, clientVersion, shareContext, mRenderer, notifyResets, robustAccess);
    return egl::Error(EGL_SUCCESS);
}

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;
    egl::Error error = CreateRendererD3D(display, &mRenderer);
    if (error.isError())
    {
        return error;
    }

    ASSERT(mDevice == nullptr);
    mDevice = new DeviceD3D(mRenderer);

#if !defined(ANGLE_ENABLE_WINDOWS_STORE)
    // Work around compile error from not defining "UNICODE" while Chromium does
    const LPSTR idcArrow = MAKEINTRESOURCEA(32512);

    std::string className = FormatString("ANGLE DisplayD3D 0x%0.8p Child Window Class", mDisplay);

    WNDCLASSA childWindowClassDesc = { 0 };
    childWindowClassDesc.style = CS_OWNDC;
    childWindowClassDesc.lpfnWndProc = IntermediateWindowProc;
    childWindowClassDesc.cbClsExtra = 0;
    childWindowClassDesc.cbWndExtra = 0;
    childWindowClassDesc.hInstance = GetModuleHandle(nullptr);
    childWindowClassDesc.hIcon = nullptr;
    childWindowClassDesc.hCursor = LoadCursorA(nullptr, idcArrow);
    childWindowClassDesc.hbrBackground = 0;
    childWindowClassDesc.lpszMenuName = nullptr;
    childWindowClassDesc.lpszClassName = className.c_str();

    mChildWindowClass = RegisterClassA(&childWindowClassDesc);
    if (!mChildWindowClass)
    {
        return egl::Error(EGL_NOT_INITIALIZED, "Failed to register child window class.");
    }
#endif

    return egl::Error(EGL_SUCCESS);
}

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

#if !defined(ANGLE_ENABLE_WINDOWS_STORE)
    if (mChildWindowClass != 0)
    {
        UnregisterClassA(reinterpret_cast<const char*>(mChildWindowClass), NULL);
        mChildWindowClass = 0;
    }
#endif
}

egl::ConfigSet DisplayD3D::generateConfigs() const
{
    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 NativeWindow::isValidNativeWindow(window);
}

void DisplayD3D::generateExtensions(egl::DisplayExtensions *outExtensions) const
{
    outExtensions->createContextRobustness = true;

    // ANGLE-specific extensions
    if (mRenderer->getShareHandleSupport())
    {
        outExtensions->d3dShareHandleClientBuffer = true;
        outExtensions->surfaceD3DTexture2DShareHandle = true;
    }

    outExtensions->querySurfacePointer = true;
    outExtensions->windowFixedSize = true;

    if (mRenderer->getPostSubBufferSupport())
    {
        outExtensions->postSubBuffer = true;
    }

    outExtensions->createContext = true;

    outExtensions->deviceQuery = true;
}

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->getRendererExtensions().textureNPOT;
}

ATOM DisplayD3D::getChildWindowClass() const
{
    return mChildWindowClass;
}

}
