/* -*- 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 "SharedSurfaceANGLE.h"

#include "GLContextEGL.h"

using namespace mozilla::gfx;

namespace mozilla {
namespace gl {

SurfaceFactory_ANGLEShareHandle*
SurfaceFactory_ANGLEShareHandle::Create(GLContext* gl,
                                        ID3D10Device1* d3d,
                                        const SurfaceCaps& caps)
{
    GLLibraryEGL* egl = &sEGLLibrary;
    if (!egl)
        return nullptr;

    if (!egl->IsExtensionSupported(
            GLLibraryEGL::ANGLE_surface_d3d_texture_2d_share_handle))
    {
        return nullptr;
    }

    return new SurfaceFactory_ANGLEShareHandle(gl, egl, d3d, caps);
}

EGLDisplay
SharedSurface_ANGLEShareHandle::Display()
{
    return mEGL->Display();
}


SharedSurface_ANGLEShareHandle::~SharedSurface_ANGLEShareHandle()
{
    mEGL->fDestroySurface(Display(), mPBuffer);
}

void
SharedSurface_ANGLEShareHandle::LockProdImpl()
{
    GLContextEGL::Cast(mGL)->SetEGLSurfaceOverride(mPBuffer);
}

void
SharedSurface_ANGLEShareHandle::UnlockProdImpl()
{
}


void
SharedSurface_ANGLEShareHandle::Fence()
{
    mGL->fFinish();
}

bool
SharedSurface_ANGLEShareHandle::WaitSync()
{
    // Since we glFinish in Fence(), we're always going to be resolved here.
    return true;
}

static void
FillPBufferAttribs_ByBits(nsTArray<EGLint>& aAttrs,
                          int redBits, int greenBits,
                          int blueBits, int alphaBits,
                          int depthBits, int stencilBits)
{
    aAttrs.Clear();

#if defined(A1) || defined(A2)
#error The temp-macro names we want are already defined.
#endif

#define A1(_x)      do { aAttrs.AppendElement(_x); } while (0)
#define A2(_x,_y)   do { A1(_x); A1(_y); } while (0)

    A2(LOCAL_EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT);
    A2(LOCAL_EGL_SURFACE_TYPE, LOCAL_EGL_PBUFFER_BIT);

    A2(LOCAL_EGL_RED_SIZE, redBits);
    A2(LOCAL_EGL_GREEN_SIZE, greenBits);
    A2(LOCAL_EGL_BLUE_SIZE, blueBits);
    A2(LOCAL_EGL_ALPHA_SIZE, alphaBits);

    A2(LOCAL_EGL_DEPTH_SIZE, depthBits);
    A2(LOCAL_EGL_STENCIL_SIZE, stencilBits);

    A1(LOCAL_EGL_NONE);
#undef A1
#undef A2
}

static void
FillPBufferAttribs_BySizes(nsTArray<EGLint>& attribs,
                           bool bpp16, bool hasAlpha,
                           int depthBits, int stencilBits)
{
    int red = 0;
    int green = 0;
    int blue = 0;
    int alpha = 0;

    if (bpp16) {
        if (hasAlpha) {
            red = green = blue = alpha = 4;
        } else {
            red = 5;
            green = 6;
            blue = 5;
        }
    } else {
        red = green = blue = 8;
        if (hasAlpha)
            alpha = 8;
    }

    FillPBufferAttribs_ByBits(attribs,
                              red, green, blue, alpha,
                              depthBits, stencilBits);
}

static EGLConfig
ChooseConfig(GLContext* gl,
             GLLibraryEGL* egl,
             const SurfaceCaps& caps)
{
    MOZ_ASSERT(egl);
    MOZ_ASSERT(caps.color);

    // We might want 24-bit depth, but we're only (fairly) sure to get 16-bit.
    int depthBits = caps.depth ? 16 : 0;
    int stencilBits = caps.stencil ? 8 : 0;

    // Ok, now we have everything.
    nsTArray<EGLint> attribs(32);
    FillPBufferAttribs_BySizes(attribs,
                               caps.bpp16, caps.alpha,
                               depthBits, stencilBits);

    // Time to try to get this config:
    EGLConfig configs[64];
    int numConfigs = sizeof(configs)/sizeof(EGLConfig);
    int foundConfigs = 0;

    if (!egl->fChooseConfig(egl->Display(),
                            attribs.Elements(),
                            configs, numConfigs,
                            &foundConfigs) ||
        !foundConfigs)
    {
        NS_WARNING("No configs found for the requested formats.");
        return EGL_NO_CONFIG;
    }

    // TODO: Pick a config progamatically instead of hoping that
    // the first config will be minimally matching our request.
    EGLConfig config = configs[0];

    if (gl->DebugMode()) {
        egl->DumpEGLConfig(config);
    }

    return config;
}


// Returns EGL_NO_SURFACE on error.
static EGLSurface CreatePBufferSurface(GLLibraryEGL* egl,
                                       EGLDisplay display,
                                       EGLConfig config,
                                       const gfx::IntSize& size)
{
    EGLint attribs[] = {
        LOCAL_EGL_WIDTH, size.width,
        LOCAL_EGL_HEIGHT, size.height,
        LOCAL_EGL_NONE
    };

    EGLSurface surface = egl->fCreatePbufferSurface(display, config, attribs);

    return surface;
}

SharedSurface_ANGLEShareHandle*
SharedSurface_ANGLEShareHandle::Create(GLContext* gl, ID3D10Device1* d3d,
                                       EGLContext context, EGLConfig config,
                                       const gfx::IntSize& size, bool hasAlpha)
{
    GLLibraryEGL* egl = &sEGLLibrary;
    MOZ_ASSERT(egl);
    MOZ_ASSERT(egl->IsExtensionSupported(
               GLLibraryEGL::ANGLE_surface_d3d_texture_2d_share_handle));

    if (!context || !config)
        return nullptr;

    EGLDisplay display = egl->Display();
    EGLSurface pbuffer = CreatePBufferSurface(egl, display, config, size);
    if (!pbuffer)
        return nullptr;


    // Declare everything before 'goto's.
    HANDLE shareHandle = nullptr;
    nsRefPtr<ID3D10Texture2D> texture;
    nsRefPtr<ID3D10ShaderResourceView> srv;

    // On failure, goto CleanUpIfFailed.
    // If |failed|, CleanUpIfFailed will clean up and return null.
    bool failed = true;
    HRESULT hr;

    // Off to the races!
    if (!egl->fQuerySurfacePointerANGLE(
            display,
            pbuffer,
            LOCAL_EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE,
            &shareHandle))
    {
        NS_ERROR("Failed to grab ShareHandle for PBuffer!");
        goto CleanUpIfFailed;
    }

    // Ok, we have a valid PBuffer with ShareHandle.
    // Let's attach it to D3D.
    hr = d3d->OpenSharedResource(shareHandle,
                                 __uuidof(ID3D10Texture2D),
                                 getter_AddRefs(texture));
    if (FAILED(hr)) {
        NS_ERROR("Failed to open shared resource!");
        goto CleanUpIfFailed;
    }

    hr = d3d->CreateShaderResourceView(texture, nullptr, getter_AddRefs(srv));
    if (FAILED(hr)) {
        NS_ERROR("Failed to create SRV!");
        goto CleanUpIfFailed;
    }

    failed = false;

CleanUpIfFailed:
    if (failed) {
        NS_WARNING("CleanUpIfFailed");
        egl->fDestroySurface(egl->Display(), pbuffer);
        return nullptr;
    }

    return new SharedSurface_ANGLEShareHandle(gl, egl,
                                              size, hasAlpha,
                                              context, pbuffer,
                                              texture, srv);
}


SurfaceFactory_ANGLEShareHandle::SurfaceFactory_ANGLEShareHandle(GLContext* gl,
                                                                 GLLibraryEGL* egl,
                                                                 ID3D10Device1* d3d,
                                                                 const SurfaceCaps& caps)
    : SurfaceFactory_GL(gl, SharedSurfaceType::EGLSurfaceANGLE, caps)
    , mProdGL(gl)
    , mEGL(egl)
    , mConsD3D(d3d)
{
    mConfig = ChooseConfig(mProdGL, mEGL, mReadCaps);
    mContext = GLContextEGL::Cast(mProdGL)->GetEGLContext();
    MOZ_ASSERT(mConfig && mContext);
}

} /* namespace gl */
} /* namespace mozilla */
