/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "SharedSurfaceD3D11Interop.h"

#include <d3d11.h>
#include "gfxPrefs.h"
#include "GLContext.h"
#include "WGLLibrary.h"
#include "nsPrintfCString.h"
#include "mozilla/gfx/DeviceManagerDx.h"

namespace mozilla {
namespace gl {

/*
Sample Code for WGL_NV_DX_interop2:
Example: Render to Direct3D 11 backbuffer with openGL:

// create D3D11 device, context and swap chain.
ID3D11Device *device;
ID3D11DeviceContext *devCtx;
IDXGISwapChain *swapChain;

DXGI_SWAP_CHAIN_DESC scd;

<set appropriate swap chain parameters in scd>

hr = D3D11CreateDeviceAndSwapChain(NULL,                        // pAdapter
                                   D3D_DRIVER_TYPE_HARDWARE,    // DriverType
                                   NULL,                        // Software
                                   0,                           // Flags (Do not set D3D11_CREATE_DEVICE_SINGLETHREADED)
                                   NULL,                        // pFeatureLevels
                                   0,                           // FeatureLevels
                                   D3D11_SDK_VERSION,           // SDKVersion
                                   &scd,                        // pSwapChainDesc
                                   &swapChain,                  // ppSwapChain
                                   &device,                     // ppDevice
                                   NULL,                        // pFeatureLevel
                                   &devCtx);                    // ppImmediateContext

// Fetch the swapchain backbuffer
ID3D11Texture2D *dxColorbuffer;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID *)&dxColorbuffer);

// Create depth stencil texture
ID3D11Texture2D *dxDepthBuffer;
D3D11_TEXTURE2D_DESC depthDesc;
depthDesc.Usage = D3D11_USAGE_DEFAULT;
<set other depthDesc parameters appropriately>

// Create Views
ID3D11RenderTargetView *colorBufferView;
D3D11_RENDER_TARGET_VIEW_DESC rtd;
<set rtd parameters appropriately>
device->CreateRenderTargetView(dxColorbuffer, &rtd, &colorBufferView);

ID3D11DepthStencilView *depthBufferView;
D3D11_DEPTH_STENCIL_VIEW_DESC dsd;
<set dsd parameters appropriately>
device->CreateDepthStencilView(dxDepthBuffer, &dsd, &depthBufferView);

// Attach back buffer and depth texture to redertarget for the device.
devCtx->OMSetRenderTargets(1, &colorBufferView, depthBufferView);

// Register D3D11 device with GL
HANDLE gl_handleD3D;
gl_handleD3D = wglDXOpenDeviceNV(device);

// register the Direct3D color and depth/stencil buffers as
// renderbuffers in opengl
GLuint gl_names[2];
HANDLE gl_handles[2];

glGenRenderbuffers(2, gl_names);

gl_handles[0] = wglDXRegisterObjectNV(gl_handleD3D, dxColorBuffer,
                                      gl_names[0],
                                      GL_RENDERBUFFER,
                                      WGL_ACCESS_READ_WRITE_NV);

gl_handles[1] = wglDXRegisterObjectNV(gl_handleD3D, dxDepthBuffer,
                                      gl_names[1],
                                      GL_RENDERBUFFER,
                                      WGL_ACCESS_READ_WRITE_NV);

// attach the Direct3D buffers to an FBO
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                          GL_RENDERBUFFER, gl_names[0]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                          GL_RENDERBUFFER, gl_names[1]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
                          GL_RENDERBUFFER, gl_names[1]);

while (!done) {
      <direct3d renders to the render targets>

      // lock the render targets for GL access
      wglDXLockObjectsNVX(gl_handleD3D, 2, gl_handles);

      <opengl renders to the render targets>

      // unlock the render targets
      wglDXUnlockObjectsNVX(gl_handleD3D, 2, gl_handles);

      <direct3d renders to the render targets and presents
       the results on the screen>
}
*/

////////////////////////////////////////////////////////////////////////////////
// DXInterop2Device

class DXInterop2Device : public RefCounted<DXInterop2Device>
{
public:
    MOZ_DECLARE_REFCOUNTED_TYPENAME(DXInterop2Device)

    WGLLibrary* const mWGL;
    const RefPtr<ID3D11Device> mD3D; // Only needed for lifetime guarantee.
    const HANDLE mInteropDevice;
    GLContext* const mGL;

    static already_AddRefed<DXInterop2Device> Open(WGLLibrary* wgl, GLContext* gl)
    {
        MOZ_ASSERT(wgl->HasDXInterop2());

        const RefPtr<ID3D11Device> d3d = gfx::DeviceManagerDx::Get()->GetContentDevice();
        if (!d3d) {
            gfxCriticalNote << "DXInterop2Device::Open: Failed to create D3D11 device.";
            return nullptr;
        }

        if (!gl->MakeCurrent())
            return nullptr;

        const auto interopDevice = wgl->fDXOpenDevice(d3d);
        if (!interopDevice) {
            gfxCriticalNote << "DXInterop2Device::Open: DXOpenDevice failed.";
            return nullptr;
        }

        return MakeAndAddRef<DXInterop2Device>(wgl, d3d, interopDevice, gl);
    }

    DXInterop2Device(WGLLibrary* wgl, ID3D11Device* d3d, HANDLE interopDevice,
                     GLContext* gl)
        : mWGL(wgl)
        , mD3D(d3d)
        , mInteropDevice(interopDevice)
        , mGL(gl)
    { }

    ~DXInterop2Device() {
        const auto isCurrent = mGL->MakeCurrent();

        if (mWGL->fDXCloseDevice(mInteropDevice))
            return;

        if (isCurrent) {
            // That shouldn't have failed.
            const uint32_t error = GetLastError();
            const nsPrintfCString errorMessage("wglDXCloseDevice(0x%p) failed:"
                                               " GetLastError(): %u\n",
                                               mInteropDevice, error);
            gfxCriticalError() << errorMessage.BeginReading();
        }
    }

    HANDLE RegisterObject(void* d3dObject, GLuint name, GLenum type,
                          GLenum access) const
    {
        if (!mGL->MakeCurrent())
            return nullptr;

        const auto ret = mWGL->fDXRegisterObject(mInteropDevice, d3dObject, name, type,
                                                 access);
        if (ret)
            return ret;

        const uint32_t error = GetLastError();
        const nsPrintfCString errorMessage("wglDXRegisterObject(0x%p, 0x%p, %u, 0x%04x,"
                                           " 0x%04x) failed: GetLastError(): %u\n",
                                           mInteropDevice, d3dObject, name, type, access,
                                           error);
        gfxCriticalNote << errorMessage.BeginReading();
        return nullptr;
    }

    bool UnregisterObject(HANDLE lockHandle) const {
        const auto isCurrent = mGL->MakeCurrent();

        if (mWGL->fDXUnregisterObject(mInteropDevice, lockHandle))
            return true;

        if (!isCurrent) {
            // That shouldn't have failed.
            const uint32_t error = GetLastError();
            const nsPrintfCString errorMessage("wglDXUnregisterObject(0x%p, 0x%p) failed:"
                                               " GetLastError(): %u\n",
                                               mInteropDevice, lockHandle, error);
            gfxCriticalError() << errorMessage.BeginReading();
        }
        return false;
    }

    bool LockObject(HANDLE lockHandle) const {
        MOZ_ASSERT(mGL->IsCurrent());

        if (mWGL->fDXLockObjects(mInteropDevice, 1, &lockHandle))
            return true;

        if (!mGL->MakeCurrent())
            return false;

        gfxCriticalNote << "wglDXLockObjects called without mGL being current."
                        << " Retrying after MakeCurrent.";

        if (mWGL->fDXLockObjects(mInteropDevice, 1, &lockHandle))
            return true;

        const uint32_t error = GetLastError();
        const nsPrintfCString errorMessage("wglDXLockObjects(0x%p, 1, {0x%p}) failed:"
                                           " GetLastError(): %u\n",
                                           mInteropDevice, lockHandle, error);
        gfxCriticalError() << errorMessage.BeginReading();
        return false;
    }

    bool UnlockObject(HANDLE lockHandle) const {
        MOZ_ASSERT(mGL->IsCurrent());

        if (mWGL->fDXUnlockObjects(mInteropDevice, 1, &lockHandle))
            return true;

        if (!mGL->MakeCurrent())
            return false;

        gfxCriticalNote << "wglDXUnlockObjects called without mGL being current."
                        << " Retrying after MakeCurrent.";

        if (mWGL->fDXUnlockObjects(mInteropDevice, 1, &lockHandle))
            return true;

        const uint32_t error = GetLastError();
        const nsPrintfCString errorMessage("wglDXUnlockObjects(0x%p, 1, {0x%p}) failed:"
                                           " GetLastError(): %u\n",
                                           mInteropDevice, lockHandle, error);
        gfxCriticalError() << errorMessage.BeginReading();
        return false;
    }
};

////////////////////////////////////////////////////////////////////////////////
// Shared Surface

/*static*/ UniquePtr<SharedSurface_D3D11Interop>
SharedSurface_D3D11Interop::Create(DXInterop2Device* interop,
                                   GLContext* gl,
                                   const gfx::IntSize& size,
                                   bool hasAlpha)
{
    const auto& d3d = interop->mD3D;

    // Create a texture in case we need to readback.
    DXGI_FORMAT format = hasAlpha ? DXGI_FORMAT_B8G8R8A8_UNORM
                                  : DXGI_FORMAT_B8G8R8X8_UNORM;
    CD3D11_TEXTURE2D_DESC desc(format, size.width, size.height, 1, 1);
    desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;

    RefPtr<ID3D11Texture2D> texD3D;
    auto hr = d3d->CreateTexture2D(&desc, nullptr, getter_AddRefs(texD3D));
    if (FAILED(hr)) {
        NS_WARNING("Failed to create texture for CanvasLayer!");
        return nullptr;
    }

    RefPtr<IDXGIResource> texDXGI;
    hr = texD3D->QueryInterface(__uuidof(IDXGIResource), getter_AddRefs(texDXGI));
    if (FAILED(hr)) {
        NS_WARNING("Failed to open texture for sharing!");
        return nullptr;
    }

    HANDLE dxgiHandle;
    texDXGI->GetSharedHandle(&dxgiHandle);

    ////

    if (!gl->MakeCurrent()) {
        NS_WARNING("MakeCurrent failed.");
        return nullptr;
    }

    GLuint rbGL = 0;
    gl->fGenRenderbuffers(1, &rbGL);
    const auto lockHandle = interop->RegisterObject(texD3D, rbGL, LOCAL_GL_RENDERBUFFER,
                                                    LOCAL_WGL_ACCESS_WRITE_DISCARD_NV);
    if (!lockHandle) {
        NS_WARNING("Failed to register D3D object with WGL.");
        gl->fDeleteRenderbuffers(1, &rbGL);
        return nullptr;
    }

    ////

    typedef SharedSurface_D3D11Interop ptrT;
    UniquePtr<ptrT> ret ( new ptrT(gl, size, hasAlpha, rbGL, interop, lockHandle,
                                   texD3D, dxgiHandle) );
    return Move(ret);
}

SharedSurface_D3D11Interop::SharedSurface_D3D11Interop(GLContext* gl,
                                                       const gfx::IntSize& size,
                                                       bool hasAlpha, GLuint rbGL,
                                                       DXInterop2Device* interop,
                                                       HANDLE lockHandle,
                                                       ID3D11Texture2D* texD3D,
                                                       HANDLE dxgiHandle)
    : SharedSurface(SharedSurfaceType::DXGLInterop2,
                    AttachmentType::GLRenderbuffer,
                    gl,
                    size,
                    hasAlpha,
                    true)
    , mProdRB(rbGL)
    , mInterop(interop)
    , mLockHandle(lockHandle)
    , mTexD3D(texD3D)
    , mDXGIHandle(dxgiHandle)
    , mNeedsFinish(gfxPrefs::WebGLDXGLNeedsFinish())
    , mLockedForGL(false)
{ }

SharedSurface_D3D11Interop::~SharedSurface_D3D11Interop()
{
    MOZ_ASSERT(!IsProducerAcquired());

    if (!mGL || !mGL->MakeCurrent())
        return;

    if (!mInterop->UnregisterObject(mLockHandle)) {
        NS_WARNING("Failed to release mLockHandle, possibly leaking it.");
    }

    mGL->fDeleteRenderbuffers(1, &mProdRB);
}

void
SharedSurface_D3D11Interop::ProducerAcquireImpl()
{
    MOZ_ASSERT(!mLockedForGL);

    // Now we have the mutex, we can lock for GL.
    MOZ_ALWAYS_TRUE( mInterop->LockObject(mLockHandle) );

    mLockedForGL = true;
}

void
SharedSurface_D3D11Interop::ProducerReleaseImpl()
{
    MOZ_ASSERT(mLockedForGL);

    if (mNeedsFinish) {
        mGL->fFinish();
    } else {
        // We probably don't even need this.
        mGL->fFlush();
    }
    MOZ_ALWAYS_TRUE( mInterop->UnlockObject(mLockHandle) );

    mLockedForGL = false;
}

bool
SharedSurface_D3D11Interop::ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_descriptor)
{
    const auto format = (mHasAlpha ? gfx::SurfaceFormat::B8G8R8A8
                                   : gfx::SurfaceFormat::B8G8R8X8);
    *out_descriptor = layers::SurfaceDescriptorD3D10(WindowsHandle(mDXGIHandle), format,
                                                     mSize);
    return true;
}

//////////////////////////////////////////////////////////////////////////////////////////
// Factory

/*static*/ UniquePtr<SurfaceFactory_D3D11Interop>
SurfaceFactory_D3D11Interop::Create(GLContext* gl, const SurfaceCaps& caps,
                                    layers::LayersIPCChannel* allocator,
                                    const layers::TextureFlags& flags)
{
    WGLLibrary* wgl = &sWGLLib;
    if (!wgl || !wgl->HasDXInterop2())
        return nullptr;

    const RefPtr<DXInterop2Device> interop = DXInterop2Device::Open(wgl, gl);
    if (!interop) {
        NS_WARNING("Failed to open D3D device for use by WGL.");
        return nullptr;
    }

    typedef SurfaceFactory_D3D11Interop ptrT;
    UniquePtr<ptrT> ret(new ptrT(gl, caps, allocator, flags, interop));
    return Move(ret);
}

SurfaceFactory_D3D11Interop::SurfaceFactory_D3D11Interop(GLContext* gl,
                                                         const SurfaceCaps& caps,
                                                         layers::LayersIPCChannel* allocator,
                                                         const layers::TextureFlags& flags,
                                                         DXInterop2Device* interop)
    : SurfaceFactory(SharedSurfaceType::DXGLInterop2, gl, caps, allocator, flags)
    , mInterop(interop)
{ }

SurfaceFactory_D3D11Interop::~SurfaceFactory_D3D11Interop()
{ }

} // namespace gl
} // namespace mozilla