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

// D3DTextureSurfaceWGL.cpp: WGL implementation of egl::Surface for D3D texture interop.

#include "libANGLE/renderer/gl/wgl/D3DTextureSurfaceWGL.h"

#include "libANGLE/Surface.h"
#include "libANGLE/renderer/gl/FramebufferGL.h"
#include "libANGLE/renderer/gl/TextureGL.h"
#include "libANGLE/renderer/gl/RendererGL.h"
#include "libANGLE/renderer/gl/StateManagerGL.h"
#include "libANGLE/renderer/gl/wgl/DisplayWGL.h"
#include "libANGLE/renderer/gl/wgl/FunctionsWGL.h"

namespace rx
{

namespace
{

egl::Error GetD3D11TextureInfo(EGLenum buftype,
                               ID3D11Texture2D *texture11,
                               size_t *width,
                               size_t *height,
                               IUnknown **object,
                               IUnknown **device)
{
    D3D11_TEXTURE2D_DESC textureDesc;
    texture11->GetDesc(&textureDesc);

    if (buftype == EGL_D3D_TEXTURE_ANGLE)
    {
        // From table egl.restrictions in EGL_ANGLE_d3d_texture_client_buffer.
        switch (textureDesc.Format)
        {
            case DXGI_FORMAT_R8G8B8A8_UNORM:
            case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
            case DXGI_FORMAT_B8G8R8A8_UNORM:
            case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
            case DXGI_FORMAT_R16G16B16A16_FLOAT:
            case DXGI_FORMAT_R32G32B32A32_FLOAT:
                break;

            default:
                SafeRelease(texture11);
                return egl::Error(EGL_BAD_PARAMETER, "Unknown client buffer texture format: %u.",
                                  textureDesc.Format);
        }
    }

    ID3D11Device *d3d11Device = nullptr;
    texture11->GetDevice(&d3d11Device);
    if (d3d11Device == nullptr)
    {
        SafeRelease(texture11);
        return egl::Error(EGL_BAD_PARAMETER,
                          "Could not query the D3D11 device from the client buffer.");
    }

    if (width)
    {
        *width = textureDesc.Width;
    }
    if (height)
    {
        *height = textureDesc.Height;
    }

    if (device)
    {
        *device = d3d11Device;
    }
    else
    {
        SafeRelease(d3d11Device);
    }

    if (object)
    {
        *object = texture11;
    }
    else
    {
        SafeRelease(texture11);
    }

    return egl::Error(EGL_SUCCESS);
}

egl::Error GetD3D9TextureInfo(EGLenum buftype,
                              IDirect3DTexture9 *texture9,
                              size_t *width,
                              size_t *height,
                              IUnknown **object,
                              IUnknown **device)
{
    D3DSURFACE_DESC surfaceDesc;
    if (FAILED(texture9->GetLevelDesc(0, &surfaceDesc)))
    {
        SafeRelease(texture9);
        return egl::Error(EGL_BAD_PARAMETER, "Could not query description of the D3D9 surface.");
    }

    if (buftype == EGL_D3D_TEXTURE_ANGLE)
    {
        // From table egl.restrictions in EGL_ANGLE_d3d_texture_client_buffer.
        switch (surfaceDesc.Format)
        {
            case D3DFMT_R8G8B8:
            case D3DFMT_A8R8G8B8:
            case D3DFMT_A16B16G16R16F:
            case D3DFMT_A32B32G32R32F:
                break;

            default:
                SafeRelease(texture9);
                return egl::Error(EGL_BAD_PARAMETER, "Unknown client buffer texture format: %u.",
                                  surfaceDesc.Format);
        }
    }

    if (width)
    {
        *width = surfaceDesc.Width;
    }
    if (height)
    {
        *height = surfaceDesc.Height;
    }

    IDirect3DDevice9 *d3d9Device = nullptr;
    HRESULT result               = texture9->GetDevice(&d3d9Device);
    if (FAILED(result))
    {
        SafeRelease(texture9);
        return egl::Error(EGL_BAD_PARAMETER,
                          "Could not query the D3D9 device from the client buffer.");
    }

    if (device)
    {
        *device = d3d9Device;
    }
    else
    {
        SafeRelease(d3d9Device);
    }

    if (object)
    {
        *object = texture9;
    }
    else
    {
        SafeRelease(texture9);
    }

    return egl::Error(EGL_SUCCESS);
}

egl::Error GetD3DTextureInfo(EGLenum buftype,
                             EGLClientBuffer clientBuffer,
                             ID3D11Device *d3d11Device,
                             size_t *width,
                             size_t *height,
                             IUnknown **object,
                             IUnknown **device)
{
    if (buftype == EGL_D3D_TEXTURE_ANGLE)
    {
        IUnknown *buffer            = static_cast<IUnknown *>(clientBuffer);
        ID3D11Texture2D *texture11  = nullptr;
        IDirect3DTexture9 *texture9 = nullptr;
        if (SUCCEEDED(buffer->QueryInterface<ID3D11Texture2D>(&texture11)))
        {
            return GetD3D11TextureInfo(buftype, texture11, width, height, object, device);
        }
        else if (SUCCEEDED(buffer->QueryInterface<IDirect3DTexture9>(&texture9)))
        {
            return GetD3D9TextureInfo(buftype, texture9, width, height, object, device);
        }
        else
        {
            return egl::Error(EGL_BAD_PARAMETER,
                              "Provided buffer is not a IDirect3DTexture9 or ID3D11Texture2D.");
        }
    }
    else if (buftype == EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE)
    {
        ASSERT(d3d11Device);
        HANDLE shareHandle         = static_cast<HANDLE>(clientBuffer);
        ID3D11Texture2D *texture11 = nullptr;
        HRESULT result = d3d11Device->OpenSharedResource(shareHandle, __uuidof(ID3D11Texture2D),
                                                         reinterpret_cast<void **>(&texture11));
        if (FAILED(result))
        {
            return egl::Error(EGL_BAD_PARAMETER, "Failed to open share handle, result: 0x%X.",
                              result);
        }

        return GetD3D11TextureInfo(buftype, texture11, width, height, object, device);
    }
    else
    {
        UNREACHABLE();
        return egl::Error(EGL_BAD_DISPLAY, "Unknown buftype for D3DTextureSurfaceWGL.");
    }
}

}  // anonymous namespace

D3DTextureSurfaceWGL::D3DTextureSurfaceWGL(const egl::SurfaceState &state,
                                           RendererGL *renderer,
                                           EGLenum buftype,
                                           EGLClientBuffer clientBuffer,
                                           DisplayWGL *display,
                                           HGLRC wglContext,
                                           HDC deviceContext,
                                           ID3D11Device *displayD3D11Device,
                                           const FunctionsGL *functionsGL,
                                           const FunctionsWGL *functionsWGL)
    : SurfaceGL(state, renderer),
      mBuftype(buftype),
      mClientBuffer(clientBuffer),
      mRenderer(renderer),
      mDisplayD3D11Device(displayD3D11Device),
      mDisplay(display),
      mStateManager(renderer->getStateManager()),
      mWorkarounds(renderer->getWorkarounds()),
      mFunctionsGL(functionsGL),
      mFunctionsWGL(functionsWGL),
      mWGLContext(wglContext),
      mDeviceContext(deviceContext),
      mWidth(0),
      mHeight(0),
      mDeviceHandle(nullptr),
      mObject(nullptr),
      mKeyedMutex(nullptr),
      mBoundObjectTextureHandle(nullptr),
      mBoundObjectRenderbufferHandle(nullptr),
      mColorRenderbufferID(0),
      mDepthStencilRenderbufferID(0),
      mFramebufferID(0)
{
}

D3DTextureSurfaceWGL::~D3DTextureSurfaceWGL()
{
    ASSERT(mBoundObjectTextureHandle == nullptr);

    SafeRelease(mObject);
    SafeRelease(mKeyedMutex);

    if (mDeviceHandle)
    {
        if (mBoundObjectRenderbufferHandle)
        {
            mFunctionsWGL->dxUnregisterObjectNV(mDeviceHandle, mBoundObjectRenderbufferHandle);
            mBoundObjectRenderbufferHandle = nullptr;
        }
        mStateManager->deleteRenderbuffer(mColorRenderbufferID);
        mStateManager->deleteRenderbuffer(mDepthStencilRenderbufferID);

        if (mBoundObjectTextureHandle)
        {
            mFunctionsWGL->dxUnregisterObjectNV(mDeviceHandle, mBoundObjectTextureHandle);
            mBoundObjectTextureHandle = nullptr;
        }

        // GL framebuffer is deleted by the default framebuffer object
        mFramebufferID = 0;

        mDisplay->releaseD3DDevice(mDeviceHandle);
        mDeviceHandle = nullptr;
    }
}

egl::Error D3DTextureSurfaceWGL::ValidateD3DTextureClientBuffer(EGLenum buftype,
                                                                EGLClientBuffer clientBuffer,
                                                                ID3D11Device *d3d11Device)
{
    return GetD3DTextureInfo(buftype, clientBuffer, d3d11Device, nullptr, nullptr, nullptr,
                             nullptr);
}

egl::Error D3DTextureSurfaceWGL::initialize(const DisplayImpl *displayImpl)
{
    IUnknown *device = nullptr;
    ANGLE_TRY(GetD3DTextureInfo(mBuftype, mClientBuffer, mDisplayD3D11Device, &mWidth, &mHeight,
                                &mObject, &device));

    // Grab the keyed mutex, if one exists
    mObject->QueryInterface(&mKeyedMutex);

    ASSERT(device != nullptr);
    egl::Error error = mDisplay->registerD3DDevice(device, &mDeviceHandle);
    SafeRelease(device);
    if (error.isError())
    {
        return error;
    }

    mFunctionsGL->genRenderbuffers(1, &mColorRenderbufferID);
    mStateManager->bindRenderbuffer(GL_RENDERBUFFER, mColorRenderbufferID);
    mBoundObjectRenderbufferHandle = mFunctionsWGL->dxRegisterObjectNV(
        mDeviceHandle, mObject, mColorRenderbufferID, GL_RENDERBUFFER, WGL_ACCESS_READ_WRITE_NV);
    if (mBoundObjectRenderbufferHandle == nullptr)
    {
        return egl::Error(EGL_BAD_ALLOC, "Failed to register D3D object, error: 0x%08x.",
                          HRESULT_CODE(GetLastError()));
    }

    const egl::Config *config = mState.config;
    if (config->depthStencilFormat != GL_NONE)
    {
        mFunctionsGL->genRenderbuffers(1, &mDepthStencilRenderbufferID);
        mStateManager->bindRenderbuffer(GL_RENDERBUFFER, mDepthStencilRenderbufferID);
        mFunctionsGL->renderbufferStorage(GL_RENDERBUFFER, config->depthStencilFormat,
                                          static_cast<GLsizei>(mWidth),
                                          static_cast<GLsizei>(mHeight));
    }

    mFunctionsGL->genFramebuffers(1, &mFramebufferID);
    mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
    mFunctionsGL->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
                                          mColorRenderbufferID);
    if (config->depthSize > 0)
    {
        mFunctionsGL->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
                                              mDepthStencilRenderbufferID);
    }
    if (config->stencilSize > 0)
    {
        mFunctionsGL->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
                                              GL_RENDERBUFFER, mDepthStencilRenderbufferID);
    }

    return egl::Error(EGL_SUCCESS);
}

egl::Error D3DTextureSurfaceWGL::makeCurrent()
{
    if (!mFunctionsWGL->makeCurrent(mDeviceContext, mWGLContext))
    {
        // TODO(geofflang): What error type here?
        return egl::Error(EGL_CONTEXT_LOST, "Failed to make the WGL context current.");
    }

    if (!mFunctionsWGL->dxLockObjectsNV(mDeviceHandle, 1, &mBoundObjectRenderbufferHandle))
    {
        DWORD error = GetLastError();
        return egl::Error(EGL_BAD_ALLOC, "Failed to lock object, error: 0x%08x.",
                          HRESULT_CODE(error));
    }

    return egl::Error(EGL_SUCCESS);
}

egl::Error D3DTextureSurfaceWGL::unMakeCurrent()
{
    if (!mFunctionsWGL->dxUnlockObjectsNV(mDeviceHandle, 1, &mBoundObjectRenderbufferHandle))
    {
        DWORD error = GetLastError();
        return egl::Error(EGL_BAD_ALLOC, "Failed to unlock object, error: 0x%08x.",
                          HRESULT_CODE(error));
    }

    return egl::Error(EGL_SUCCESS);
}

egl::Error D3DTextureSurfaceWGL::swap(const DisplayImpl *displayImpl)
{
    return egl::Error(EGL_SUCCESS);
}

egl::Error D3DTextureSurfaceWGL::postSubBuffer(EGLint x, EGLint y, EGLint width, EGLint height)
{
    UNIMPLEMENTED();
    return egl::Error(EGL_SUCCESS);
}

egl::Error D3DTextureSurfaceWGL::querySurfacePointerANGLE(EGLint attribute, void **value)
{
    switch (attribute)
    {
        case EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE:
            *value = (mBuftype == EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE) ? mClientBuffer : nullptr;
            break;

        case EGL_DXGI_KEYED_MUTEX_ANGLE:
            *value = mKeyedMutex;
            break;

        default:
            UNREACHABLE();
    }

    return egl::Error(EGL_SUCCESS);
}

egl::Error D3DTextureSurfaceWGL::bindTexImage(gl::Texture *texture, EGLint buffer)
{
    ASSERT(mBoundObjectTextureHandle == nullptr);

    const TextureGL *textureGL = GetImplAs<TextureGL>(texture);
    GLuint textureID           = textureGL->getTextureID();

    mBoundObjectTextureHandle = mFunctionsWGL->dxRegisterObjectNV(
        mDeviceHandle, mObject, textureID, GL_TEXTURE_2D, WGL_ACCESS_READ_WRITE_NV);
    if (mBoundObjectTextureHandle == nullptr)
    {
        DWORD error = GetLastError();
        return egl::Error(EGL_BAD_ALLOC, "Failed to register D3D object, error: 0x%08x.",
                          HRESULT_CODE(error));
    }

    if (!mFunctionsWGL->dxLockObjectsNV(mDeviceHandle, 1, &mBoundObjectTextureHandle))
    {
        DWORD error = GetLastError();
        return egl::Error(EGL_BAD_ALLOC, "Failed to lock object, error: 0x%08x.",
                          HRESULT_CODE(error));
    }

    return egl::Error(EGL_SUCCESS);
}

egl::Error D3DTextureSurfaceWGL::releaseTexImage(EGLint buffer)
{
    ASSERT(mBoundObjectTextureHandle != nullptr);
    if (!mFunctionsWGL->dxUnlockObjectsNV(mDeviceHandle, 1, &mBoundObjectTextureHandle))
    {
        DWORD error = GetLastError();
        return egl::Error(EGL_BAD_ALLOC, "Failed to unlock object, error: 0x%08x.",
                          HRESULT_CODE(error));
    }

    if (!mFunctionsWGL->dxUnregisterObjectNV(mDeviceHandle, mBoundObjectTextureHandle))
    {
        DWORD error = GetLastError();
        return egl::Error(EGL_BAD_ALLOC, "Failed to unregister D3D object, error: 0x%08x.",
                          HRESULT_CODE(error));
    }
    mBoundObjectTextureHandle = nullptr;

    return egl::Error(EGL_SUCCESS);
}

void D3DTextureSurfaceWGL::setSwapInterval(EGLint interval)
{
    UNIMPLEMENTED();
}

EGLint D3DTextureSurfaceWGL::getWidth() const
{
    return static_cast<EGLint>(mWidth);
}

EGLint D3DTextureSurfaceWGL::getHeight() const
{
    return static_cast<EGLint>(mHeight);
}

EGLint D3DTextureSurfaceWGL::isPostSubBufferSupported() const
{
    return EGL_FALSE;
}

EGLint D3DTextureSurfaceWGL::getSwapBehavior() const
{
    return EGL_BUFFER_PRESERVED;
}

FramebufferImpl *D3DTextureSurfaceWGL::createDefaultFramebuffer(const gl::FramebufferState &data)
{
    return new FramebufferGL(mFramebufferID, data, mFunctionsGL, mWorkarounds,
                             mRenderer->getBlitter(), mStateManager);
}
}  // namespace rx
