/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=80: */
/* 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 "GLBlitHelper.h"

#include <d3d11.h>

#include "GLContext.h"
#include "GLLibraryEGL.h"
#include "GPUVideoImage.h"
#include "ScopedGLHelpers.h"

#include "mozilla/layers/D3D11YCbCrImage.h"
#include "mozilla/layers/TextureD3D11.h"

namespace mozilla {
namespace gl {

static EGLStreamKHR
StreamFromD3DTexture(ID3D11Texture2D* const texD3D,
                     const EGLAttrib* const postAttribs)
{
    auto& egl = sEGLLibrary;
    if (!egl.IsExtensionSupported(GLLibraryEGL::NV_stream_consumer_gltexture_yuv) ||
        !egl.IsExtensionSupported(GLLibraryEGL::ANGLE_stream_producer_d3d_texture_nv12))
    {
        return 0;
    }

    const auto& display = egl.Display();
    const auto stream = egl.fCreateStreamKHR(display, nullptr);
    MOZ_ASSERT(stream);
    if (!stream)
        return 0;
    bool ok = true;
    MOZ_ALWAYS_TRUE( ok &= bool(egl.fStreamConsumerGLTextureExternalAttribsNV(display,
                                                                              stream,
                                                                              nullptr)) );
    MOZ_ALWAYS_TRUE( ok &= bool(egl.fCreateStreamProducerD3DTextureNV12ANGLE(display,
                                                                             stream,
                                                                             nullptr)) );
    MOZ_ALWAYS_TRUE( ok &= bool(egl.fStreamPostD3DTextureNV12ANGLE(display, stream,
                                                                   texD3D,
                                                                   postAttribs)) );
    if (ok)
        return stream;

    (void)egl.fDestroyStreamKHR(display, stream);
    return 0;
}

static RefPtr<ID3D11Texture2D>
OpenSharedTexture(ID3D11Device* const d3d, const WindowsHandle handle)
{
    RefPtr<ID3D11Texture2D> tex;
    auto hr = d3d->OpenSharedResource((HANDLE)handle, __uuidof(ID3D11Texture2D),
                                      (void**)(ID3D11Texture2D**)getter_AddRefs(tex));
    if (FAILED(hr)) {
        gfxCriticalError() << "Error code from OpenSharedResource: " << gfx::hexa(hr);
        return nullptr;
    }
    return tex;
}

// -------------------------------------

class BindAnglePlanes final
{
    const GLBlitHelper& mParent;
    const uint8_t mNumPlanes;
    const ScopedSaveMultiTex mMultiTex;
    GLuint mTempTexs[3];
    EGLStreamKHR mStreams[3];
    RefPtr<IDXGIKeyedMutex> mMutexList[3];
    bool mSuccess;

public:
    BindAnglePlanes(const GLBlitHelper* const parent, const uint8_t numPlanes,
                    const RefPtr<ID3D11Texture2D>* const texD3DList,
                    const EGLAttrib* const* postAttribsList = nullptr)
        : mParent(*parent)
        , mNumPlanes(numPlanes)
        , mMultiTex(mParent.mGL, mNumPlanes, LOCAL_GL_TEXTURE_EXTERNAL)
        , mTempTexs{0}
        , mStreams{0}
        , mSuccess(true)
    {
        MOZ_RELEASE_ASSERT(numPlanes >= 1 && numPlanes <= 3);

        const auto& gl = mParent.mGL;
        auto& egl = sEGLLibrary;
        const auto& display = egl.Display();

        gl->fGenTextures(numPlanes, mTempTexs);

        for (uint8_t i = 0; i < mNumPlanes; i++) {
            gl->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
            gl->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL, mTempTexs[i]);
            const EGLAttrib* postAttribs = nullptr;
            if (postAttribsList) {
                postAttribs = postAttribsList[i];
            }
            mStreams[i] = StreamFromD3DTexture(texD3DList[i], postAttribs);
            mSuccess &= bool(mStreams[i]);
        }

        if (mSuccess) {
            for (uint8_t i = 0; i < mNumPlanes; i++) {
                MOZ_ALWAYS_TRUE( egl.fStreamConsumerAcquireKHR(display, mStreams[i]) );

                auto& mutex = mMutexList[i];
                texD3DList[i]->QueryInterface(_uuidof(IDXGIKeyedMutex),
                                              (void**)getter_AddRefs(mutex));
                if (mutex) {
                    const auto hr = mutex->AcquireSync(0, 100);
                    if (FAILED(hr)) {
                        NS_WARNING("BindAnglePlanes failed to acquire KeyedMutex.");
                        mSuccess = false;
                    }
                }
            }
        }
    }

    ~BindAnglePlanes()
    {
        const auto& gl = mParent.mGL;
        auto& egl = sEGLLibrary;
        const auto& display = egl.Display();

        if (mSuccess) {
            for (uint8_t i = 0; i < mNumPlanes; i++) {
                MOZ_ALWAYS_TRUE( egl.fStreamConsumerReleaseKHR(display, mStreams[i]) );
                if (mMutexList[i]) {
                    mMutexList[i]->ReleaseSync(0);
                }
            }
        }

        for (uint8_t i = 0; i < mNumPlanes; i++) {
            (void)egl.fDestroyStreamKHR(display, mStreams[i]);
        }

        gl->fDeleteTextures(mNumPlanes, mTempTexs);
    }

    const bool& Success() const { return mSuccess; }
};

// -------------------------------------

ID3D11Device*
GLBlitHelper::GetD3D11() const
{
    if (mD3D11)
        return mD3D11;

    if (!mGL->IsANGLE())
        return nullptr;

    auto& egl = sEGLLibrary;
    EGLDeviceEXT deviceEGL = 0;
    MOZ_ALWAYS_TRUE( egl.fQueryDisplayAttribEXT(egl.Display(), LOCAL_EGL_DEVICE_EXT,
                                                (EGLAttrib*)&deviceEGL) );
    if (!egl.fQueryDeviceAttribEXT(deviceEGL, LOCAL_EGL_D3D11_DEVICE_ANGLE,
                                   (EGLAttrib*)(ID3D11Device**)getter_AddRefs(mD3D11)))
    {
        MOZ_ASSERT(false, "d3d9?");
        return nullptr;
    }
    return mD3D11;
}

// -------------------------------------

bool
GLBlitHelper::BlitImage(layers::GPUVideoImage* const srcImage,
                        const gfx::IntSize& destSize, const OriginPos destOrigin) const
{
    const auto& data = srcImage->GetData();
    if (!data)
        return false;

    const auto& desc = data->SD();
    const auto& subdescUnion = desc.subdesc();
    switch (subdescUnion.type()) {
    case subdescUnion.TSurfaceDescriptorD3D10:
        {
            const auto& subdesc = subdescUnion.get_SurfaceDescriptorD3D10();
            return BlitDescriptor(subdesc, destSize, destOrigin);
        }
    case subdescUnion.TSurfaceDescriptorDXGIYCbCr:
        {
            const auto& subdesc = subdescUnion.get_SurfaceDescriptorDXGIYCbCr();

            const auto& clipSize = subdesc.size();
            const auto& ySize = subdesc.sizeY();
            const auto& uvSize = subdesc.sizeCbCr();

            const gfx::IntRect clipRect(0, 0, clipSize.width, clipSize.height);
            const auto colorSpace = YUVColorSpace::BT601;

            const WindowsHandle handles[3] = {
                subdesc.handleY(),
                subdesc.handleCb(),
                subdesc.handleCr()
            };
            return BlitAngleYCbCr(handles, clipRect, ySize, uvSize, colorSpace, destSize,
                                  destOrigin);
        }
    default:
        gfxCriticalError() << "Unhandled subdesc type: " << uint32_t(subdescUnion.type());
        return false;
    }
}

// -------------------------------------

bool
GLBlitHelper::BlitImage(layers::D3D11YCbCrImage* const srcImage,
                        const gfx::IntSize& destSize, const OriginPos destOrigin) const
{
    const auto& data = srcImage->GetData();
    if (!data)
        return false;

    const auto& clipRect = srcImage->mPictureRect;
    const auto& colorSpace = srcImage->mColorSpace;

    const WindowsHandle handles[3] = {
        (WindowsHandle)data->mHandles[0],
        (WindowsHandle)data->mHandles[1],
        (WindowsHandle)data->mHandles[2]
    };
    return BlitAngleYCbCr(handles, srcImage->mPictureRect, srcImage->mYSize,
                          srcImage->mCbCrSize, srcImage->mColorSpace, destSize,
                          destOrigin);
}

// -------------------------------------

bool
GLBlitHelper::BlitDescriptor(const layers::SurfaceDescriptorD3D10& desc,
                             const gfx::IntSize& destSize, OriginPos destOrigin) const
{
    const auto& d3d = GetD3D11();
    if (!d3d)
        return false;

    const auto& handle = desc.handle();
    const auto& format = desc.format();
    const auto& clipSize = desc.size();

    const auto srcOrigin = OriginPos::BottomLeft;
    const gfx::IntRect clipRect(0, 0, clipSize.width, clipSize.height);
    const auto colorSpace = YUVColorSpace::BT601;

    if (format != gfx::SurfaceFormat::NV12) {
        gfxCriticalError() << "Non-NV12 format for SurfaceDescriptorD3D10: "
                           << uint32_t(format);
        return nullptr;
    }

    const auto tex = OpenSharedTexture(d3d, handle);
    if (!tex) {
        MOZ_ASSERT(false, "Get a nullptr from OpenSharedResource.");
        return false;
    }
    const RefPtr<ID3D11Texture2D> texList[2] = { tex, tex };
    const EGLAttrib postAttribs0[] = {
        LOCAL_EGL_NATIVE_BUFFER_PLANE_OFFSET_IMG, 0,
        LOCAL_EGL_NONE
    };
    const EGLAttrib postAttribs1[] = {
        LOCAL_EGL_NATIVE_BUFFER_PLANE_OFFSET_IMG, 1,
        LOCAL_EGL_NONE
    };
    const EGLAttrib* const postAttribsList[2] = { postAttribs0, postAttribs1 };
    // /layers/d3d11/CompositorD3D11.cpp uses bt601 for EffectTypes::NV12.
    //return BlitAngleNv12(tex, YUVColorSpace::BT601, destSize, destOrigin);

    const BindAnglePlanes bindPlanes(this, 2, texList, postAttribsList);

    D3D11_TEXTURE2D_DESC texDesc = {0};
    tex->GetDesc(&texDesc);

    const gfx::IntSize ySize(texDesc.Width, texDesc.Height);
    const gfx::IntSize divisors(2, 2);
    MOZ_ASSERT(ySize.width % divisors.width == 0);
    MOZ_ASSERT(ySize.height % divisors.height == 0);
    const gfx::IntSize uvSize(ySize.width / divisors.width,
                              ySize.height / divisors.height);

    const bool yFlip = destOrigin != srcOrigin;
    const DrawBlitProg::BaseArgs baseArgs = { destSize, yFlip, clipRect, ySize };
    const DrawBlitProg::YUVArgs yuvArgs = { uvSize, divisors, colorSpace };

    const auto& prog = GetDrawBlitProg({kFragHeader_TexExt, kFragBody_NV12});
    MOZ_RELEASE_ASSERT(prog);
    prog->Draw(baseArgs, &yuvArgs);
    return true;
}

// --

bool
GLBlitHelper::BlitAngleYCbCr(const WindowsHandle (&handleList)[3],
                             const gfx::IntRect& clipRect,
                             const gfx::IntSize& ySize, const gfx::IntSize& uvSize,
                             const YUVColorSpace colorSpace,
                             const gfx::IntSize& destSize, OriginPos destOrigin) const
{
    const auto& d3d = GetD3D11();
    if (!d3d)
        return false;

    const auto srcOrigin = OriginPos::BottomLeft;

    gfx::IntSize divisors;
    if (!GuessDivisors(ySize, uvSize, &divisors))
        return false;

    const RefPtr<ID3D11Texture2D> texList[3] = {
        OpenSharedTexture(d3d, handleList[0]),
        OpenSharedTexture(d3d, handleList[1]),
        OpenSharedTexture(d3d, handleList[2])
    };
    const BindAnglePlanes bindPlanes(this, 3, texList);

    const bool yFlip = destOrigin != srcOrigin;
    const DrawBlitProg::BaseArgs baseArgs = { destSize, yFlip, clipRect, ySize };
    const DrawBlitProg::YUVArgs yuvArgs = { uvSize, divisors, colorSpace };

    const auto& prog = GetDrawBlitProg({kFragHeader_TexExt, kFragBody_PlanarYUV});
    MOZ_RELEASE_ASSERT(prog);
    prog->Draw(baseArgs, &yuvArgs);
    return true;
}

} // namespace gl
} // namespace mozilla
