/* -*- 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 "GLContext.h"
#include "WGLLibrary.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>
}
*/

////////////////////////////////////////////////////////////////////////////////
// DXGL Device

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

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

    static already_AddRefed<DXGLDevice> Open(WGLLibrary* wgl)
    {
        MOZ_ASSERT(wgl->HasDXInterop2());
        gfxWindowsPlatform* plat = gfxWindowsPlatform::GetPlatform();

        RefPtr<ID3D11Device> d3d = plat->GetD3D11ContentDevice();
        if (!d3d) {
            NS_WARNING("Failed to create D3D11 device.");
            return nullptr;
        }

        HANDLE dxglDeviceHandle = wgl->fDXOpenDevice(d3d);
        if (!dxglDeviceHandle) {
            NS_WARNING("Failed to open D3D device for use by WGL.");
            return nullptr;
        }

        return MakeAndAddRef<DXGLDevice>(wgl, d3d, dxglDeviceHandle);
    }

    DXGLDevice(WGLLibrary* wgl, const RefPtr<ID3D11Device>& d3d, HANDLE dxglDeviceHandle)
        : mWGL(wgl)
        , mD3D(d3d)
        , mDXGLDeviceHandle(dxglDeviceHandle)
    { }

    ~DXGLDevice() {
        if (!mWGL->fDXCloseDevice(mDXGLDeviceHandle)) {
#ifdef DEBUG
            uint32_t error = GetLastError();
            printf_stderr("wglDXCloseDevice(0x%x) failed: GetLastError(): 0x%x\n",
                          mDXGLDeviceHandle, error);
#endif
            MOZ_CRASH();
        }
    }

    HANDLE RegisterObject(void* dxObject, GLuint name, GLenum type, GLenum access) const {
        HANDLE ret = mWGL->fDXRegisterObject(mDXGLDeviceHandle, dxObject, name, type,
                                             access);
        if (!ret) {
#ifdef DEBUG
            uint32_t error = GetLastError();
            printf_stderr("wglDXRegisterObject(0x%x, 0x%x, %u, 0x%x, 0x%x) failed:"
                          " GetLastError(): 0x%x\n", mDXGLDeviceHandle, dxObject, name,
                          type, access, error);
#endif
            MOZ_CRASH();
        }
        return ret;
    }

    bool UnregisterObject(HANDLE hObject) const {
        bool ret = mWGL->fDXUnregisterObject(mDXGLDeviceHandle, hObject);
        if (!ret) {
#ifdef DEBUG
            uint32_t error = GetLastError();
            printf_stderr("wglDXUnregisterObject(0x%x, 0x%x) failed: GetLastError():"
                          " 0x%x\n", mDXGLDeviceHandle, hObject, error);
#endif
            MOZ_CRASH();
        }
        return ret;
    }

    bool LockObject(HANDLE hObject) const {
        bool ret = mWGL->fDXLockObjects(mDXGLDeviceHandle, 1, &hObject);
        if (!ret) {
#ifdef DEBUG
            uint32_t error = GetLastError();
            printf_stderr("wglDXLockObjects(0x%x, 1, {0x%x}) failed: GetLastError():"
                          " 0x%x\n", mDXGLDeviceHandle, hObject, error);
#endif
            MOZ_CRASH();
        }
        return ret;
    }

    bool UnlockObject(HANDLE hObject) const {
        bool ret = mWGL->fDXUnlockObjects(mDXGLDeviceHandle, 1, &hObject);
        if (!ret) {
#ifdef DEBUG
            uint32_t error = GetLastError();
            printf_stderr("wglDXUnlockObjects(0x%x, 1, {0x%x}) failed: GetLastError():"
                          " 0x%x\n", mDXGLDeviceHandle, hObject, error);
#endif
            MOZ_CRASH();
        }
        return ret;
    }
};

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

/*static*/ UniquePtr<SharedSurface_D3D11Interop>
SharedSurface_D3D11Interop::Create(const RefPtr<DXGLDevice>& dxgl,
                                   GLContext* gl,
                                   const gfx::IntSize& size,
                                   bool hasAlpha)
{
    auto& d3d = *dxgl->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> textureD3D;
    HRESULT hr = d3d.CreateTexture2D(&desc, nullptr, getter_AddRefs(textureD3D));
    if (FAILED(hr)) {
        NS_WARNING("Failed to create texture for CanvasLayer!");
        return nullptr;
    }

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

    RefPtr<IDXGIKeyedMutex> keyedMutex;
    hr = textureD3D->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(keyedMutex));
    if (FAILED(hr)) {
        NS_WARNING("Failed to obtained keyed mutex from texture!");
        return nullptr;
    }

    HANDLE sharedHandle;
    textureDXGI->GetSharedHandle(&sharedHandle);

    GLuint renderbufferGL = 0;
    gl->MakeCurrent();
    gl->fGenRenderbuffers(1, &renderbufferGL);
    HANDLE objectWGL = dxgl->RegisterObject(textureD3D, renderbufferGL,
                                            LOCAL_GL_RENDERBUFFER,
                                            LOCAL_WGL_ACCESS_WRITE_DISCARD_NV);
    if (!objectWGL) {
        NS_WARNING("Failed to register D3D object with WGL.");
        return nullptr;
    }

    GLuint fence = 0;
    if (gl->IsExtensionSupported(GLContext::NV_fence)) {
        gl->MakeCurrent();
        gl->fGenFences(1, &fence);
    }

    typedef SharedSurface_D3D11Interop ptrT;
    UniquePtr<ptrT> ret ( new ptrT(gl, size, hasAlpha, renderbufferGL, dxgl, objectWGL,
                                   textureD3D, sharedHandle, keyedMutex, fence) );
    return Move(ret);
}

SharedSurface_D3D11Interop::SharedSurface_D3D11Interop(GLContext* gl,
                                                       const gfx::IntSize& size,
                                                       bool hasAlpha,
                                                       GLuint renderbufferGL,
                                                       const RefPtr<DXGLDevice>& dxgl,
                                                       HANDLE objectWGL,
                                                       const RefPtr<ID3D11Texture2D>& textureD3D,
                                                       HANDLE sharedHandle,
                                                       const RefPtr<IDXGIKeyedMutex>& keyedMutex,
                                                       GLuint fence)
    : SharedSurface(SharedSurfaceType::DXGLInterop2,
                    AttachmentType::GLRenderbuffer,
                    gl,
                    size,
                    hasAlpha,
                    true)
    , mProdRB(renderbufferGL)
    , mDXGL(dxgl)
    , mObjectWGL(objectWGL)
    , mTextureD3D(textureD3D)
    , mSharedHandle(sharedHandle)
    , mKeyedMutex(keyedMutex)
    , mFence(fence)
    , mLockedForGL(false)
{ }

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

    if (!mDXGL->UnregisterObject(mObjectWGL)) {
        NS_WARNING("Failed to release a DXGL object, possibly leaking it.");
    }

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

    if (mFence) {
        mGL->fDeleteFences(1, &mFence);
    }

    mGL->fDeleteRenderbuffers(1, &mProdRB);

    // mDXGL is closed when it runs out of refs.
}

void
SharedSurface_D3D11Interop::LockProdImpl()
{ }

void
SharedSurface_D3D11Interop::UnlockProdImpl()
{ }

void
SharedSurface_D3D11Interop::Fence()
{
    // TODO fence properly. This kills performance.
    mGL->fFinish();
}

bool
SharedSurface_D3D11Interop::WaitSync()
{
    return true;
}

bool
SharedSurface_D3D11Interop::PollSync()
{
    return true;
}

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

    if (mKeyedMutex) {
        const uint64_t keyValue = 0;
        const DWORD timeoutMs = 10000;
        HRESULT hr = mKeyedMutex->AcquireSync(keyValue, timeoutMs);
        if (hr == WAIT_TIMEOUT) {
            // Doubt we should do this? Maybe Wait for ever?
            MOZ_CRASH("d3d11Interop timeout");
        }
    }

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

    mLockedForGL = true;
}

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

    mGL->fFlush();
    MOZ_ALWAYS_TRUE(mDXGL->UnlockObject(mObjectWGL));

    mLockedForGL = false;

    // Now we have unlocked for GL, we can release to consumer.
    if (mKeyedMutex) {
        mKeyedMutex->ReleaseSync(0);
    }
    Fence();
}

void
SharedSurface_D3D11Interop::ConsumerAcquireImpl()
{
    if (!mConsumerTexture) {
        RefPtr<ID3D11Texture2D> tex;
        RefPtr<ID3D11Device> device = gfxWindowsPlatform::GetPlatform()->GetD3D11Device();
        HRESULT hr = device->OpenSharedResource(mSharedHandle,
                                                __uuidof(ID3D11Texture2D),
                                                (void**)(ID3D11Texture2D**) getter_AddRefs(tex));
        if (SUCCEEDED(hr)) {
            mConsumerTexture = tex;
            RefPtr<IDXGIKeyedMutex> mutex;
            hr = tex->QueryInterface((IDXGIKeyedMutex**) getter_AddRefs(mutex));

            if (SUCCEEDED(hr)) {
                mConsumerKeyedMutex = mutex;
            }
        }
    }

    if (mConsumerKeyedMutex) {
        const uint64_t keyValue = 0;
        const DWORD timeoutMs = 10000;
        HRESULT hr = mConsumerKeyedMutex->AcquireSync(keyValue, timeoutMs);
        if (hr == WAIT_TIMEOUT) {
            MOZ_CRASH();
        }
    }
}

void
SharedSurface_D3D11Interop::ConsumerReleaseImpl()
{
    if (mConsumerKeyedMutex) {
        mConsumerKeyedMutex->ReleaseSync(0);
    }
}

void
SharedSurface_D3D11Interop::Fence_ContentThread_Impl()
{
    if (mFence) {
        MOZ_ASSERT(mGL->IsExtensionSupported(GLContext::NV_fence));
        mGL->fSetFence(mFence, LOCAL_GL_ALL_COMPLETED_NV);
        mGL->fFlush();
        return;
    }

    Fence();
}

bool
SharedSurface_D3D11Interop::WaitSync_ContentThread_Impl()
{
    if (mFence) {
        mGL->MakeCurrent();
        mGL->fFinishFence(mFence);
        return true;
    }

    return WaitSync();
}

bool
SharedSurface_D3D11Interop::PollSync_ContentThread_Impl()
{
    if (mFence) {
        mGL->MakeCurrent();
        return mGL->fTestFence(mFence);
    }

    return PollSync();
}

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

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

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

    RefPtr<DXGLDevice> dxgl = DXGLDevice::Open(wgl);
    if (!dxgl) {
        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, dxgl));

    return Move(ret);
}

SurfaceFactory_D3D11Interop::SurfaceFactory_D3D11Interop(GLContext* gl,
                                                         const SurfaceCaps& caps,
                                                         const RefPtr<layers::ISurfaceAllocator>& allocator,
                                                         const layers::TextureFlags& flags,
                                                         const RefPtr<DXGLDevice>& dxgl)
    : SurfaceFactory(SharedSurfaceType::DXGLInterop2, gl, caps, allocator, flags)
    , mDXGL(dxgl)
{ }

SurfaceFactory_D3D11Interop::~SurfaceFactory_D3D11Interop()
{ }

} // namespace gl
} // namespace mozilla
