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

// RenderTarget11.cpp: Implements a DX11-specific wrapper for ID3D11View pointers
// retained by Renderbuffers.

#include "libANGLE/renderer/d3d/d3d11/RenderTarget11.h"

#include "libANGLE/renderer/d3d/d3d11/formatutils11.h"
#include "libANGLE/renderer/d3d/d3d11/Renderer11.h"
#include "libANGLE/renderer/d3d/d3d11/renderer11_utils.h"
#include "libANGLE/renderer/d3d/d3d11/SwapChain11.h"
#include "libANGLE/renderer/d3d/d3d11/texture_format_table.h"

namespace rx
{

static bool getTextureProperties(ID3D11Resource *resource, unsigned int *mipLevels, unsigned int *samples)
{
    ID3D11Texture1D *texture1D = d3d11::DynamicCastComObject<ID3D11Texture1D>(resource);
    if (texture1D)
    {
        D3D11_TEXTURE1D_DESC texDesc;
        texture1D->GetDesc(&texDesc);
        SafeRelease(texture1D);

        *mipLevels = texDesc.MipLevels;
        *samples = 0;

        return true;
    }

    ID3D11Texture2D *texture2D = d3d11::DynamicCastComObject<ID3D11Texture2D>(resource);
    if (texture2D)
    {
        D3D11_TEXTURE2D_DESC texDesc;
        texture2D->GetDesc(&texDesc);
        SafeRelease(texture2D);

        *mipLevels = texDesc.MipLevels;
        *samples = texDesc.SampleDesc.Count > 1 ? texDesc.SampleDesc.Count : 0;

        return true;
    }

    ID3D11Texture3D *texture3D = d3d11::DynamicCastComObject<ID3D11Texture3D>(resource);
    if (texture3D)
    {
        D3D11_TEXTURE3D_DESC texDesc;
        texture3D->GetDesc(&texDesc);
        SafeRelease(texture3D);

        *mipLevels = texDesc.MipLevels;
        *samples = 0;

        return true;
    }

    return false;
}

static unsigned int getRTVSubresourceIndex(ID3D11Resource *resource, ID3D11RenderTargetView *view)
{
    D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
    view->GetDesc(&rtvDesc);

    unsigned int mipSlice = 0;
    unsigned int arraySlice = 0;

    switch (rtvDesc.ViewDimension)
    {
      case D3D11_RTV_DIMENSION_TEXTURE1D:
        mipSlice = rtvDesc.Texture1D.MipSlice;
        arraySlice = 0;
        break;

      case D3D11_RTV_DIMENSION_TEXTURE1DARRAY:
        mipSlice = rtvDesc.Texture1DArray.MipSlice;
        arraySlice = rtvDesc.Texture1DArray.FirstArraySlice;
        break;

      case D3D11_RTV_DIMENSION_TEXTURE2D:
        mipSlice = rtvDesc.Texture2D.MipSlice;
        arraySlice = 0;
        break;

      case D3D11_RTV_DIMENSION_TEXTURE2DARRAY:
        mipSlice = rtvDesc.Texture2DArray.MipSlice;
        arraySlice = rtvDesc.Texture2DArray.FirstArraySlice;
        break;

      case D3D11_RTV_DIMENSION_TEXTURE2DMS:
        mipSlice = 0;
        arraySlice = 0;
        break;

      case D3D11_RTV_DIMENSION_TEXTURE2DMSARRAY:
        mipSlice = 0;
        arraySlice = rtvDesc.Texture2DMSArray.FirstArraySlice;
        break;

      case D3D11_RTV_DIMENSION_TEXTURE3D:
        mipSlice = rtvDesc.Texture3D.MipSlice;
        arraySlice = 0;
        break;

      case D3D11_RTV_DIMENSION_UNKNOWN:
      case D3D11_RTV_DIMENSION_BUFFER:
        UNIMPLEMENTED();
        break;

      default:
        UNREACHABLE();
        break;
    }

    unsigned int mipLevels, samples;
    getTextureProperties(resource,  &mipLevels, &samples);

    return D3D11CalcSubresource(mipSlice, arraySlice, mipLevels);
}

static unsigned int getDSVSubresourceIndex(ID3D11Resource *resource, ID3D11DepthStencilView *view)
{
    D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
    view->GetDesc(&dsvDesc);

    unsigned int mipSlice = 0;
    unsigned int arraySlice = 0;

    switch (dsvDesc.ViewDimension)
    {
      case D3D11_DSV_DIMENSION_TEXTURE1D:
        mipSlice = dsvDesc.Texture1D.MipSlice;
        arraySlice = 0;
        break;

      case D3D11_DSV_DIMENSION_TEXTURE1DARRAY:
        mipSlice = dsvDesc.Texture1DArray.MipSlice;
        arraySlice = dsvDesc.Texture1DArray.FirstArraySlice;
        break;

      case D3D11_DSV_DIMENSION_TEXTURE2D:
        mipSlice = dsvDesc.Texture2D.MipSlice;
        arraySlice = 0;
        break;

      case D3D11_DSV_DIMENSION_TEXTURE2DARRAY:
        mipSlice = dsvDesc.Texture2DArray.MipSlice;
        arraySlice = dsvDesc.Texture2DArray.FirstArraySlice;
        break;

      case D3D11_DSV_DIMENSION_TEXTURE2DMS:
        mipSlice = 0;
        arraySlice = 0;
        break;

      case D3D11_DSV_DIMENSION_TEXTURE2DMSARRAY:
        mipSlice = 0;
        arraySlice = dsvDesc.Texture2DMSArray.FirstArraySlice;
        break;

      case D3D11_DSV_DIMENSION_UNKNOWN:
        UNIMPLEMENTED();
        break;

      default:
        UNREACHABLE();
        break;
    }

    unsigned int mipLevels, samples;
    getTextureProperties(resource, &mipLevels, &samples);

    return D3D11CalcSubresource(mipSlice, arraySlice, mipLevels);
}

RenderTarget11::RenderTarget11(d3d11::ANGLEFormat angleFormat) : mANGLEFormat(angleFormat)
{
}

RenderTarget11::~RenderTarget11()
{
    signalDirty();
}

void RenderTarget11::addDirtyCallback(const NotificationCallback *callback)
{
    mDirtyCallbacks.insert(callback);
}

void RenderTarget11::removeDirtyCallback(const NotificationCallback *callback)
{
    mDirtyCallbacks.erase(callback);
}

void RenderTarget11::signalDirty()
{
    if (mDirtyCallbacks.empty())
        return;

    for (const auto &callback : mDirtyCallbacks)
    {
        (*callback)();
    }

    // Clear the signal list. We can't do this in the callback because it mutates the iterator.
    mDirtyCallbacks.clear();
}

TextureRenderTarget11::TextureRenderTarget11(ID3D11RenderTargetView *rtv,
                                             ID3D11Resource *resource,
                                             ID3D11ShaderResourceView *srv,
                                             ID3D11ShaderResourceView *blitSRV,
                                             GLenum internalFormat,
                                             d3d11::ANGLEFormat angleFormat,
                                             GLsizei width,
                                             GLsizei height,
                                             GLsizei depth,
                                             GLsizei samples)
    : RenderTarget11(angleFormat),
      mWidth(width),
      mHeight(height),
      mDepth(depth),
      mInternalFormat(internalFormat),
      mSamples(samples),
      mSubresourceIndex(0),
      mTexture(resource),
      mRenderTarget(rtv),
      mDepthStencil(NULL),
      mShaderResource(srv),
      mBlitShaderResource(blitSRV)
{
    if (mTexture)
    {
        mTexture->AddRef();
    }

    if (mRenderTarget)
    {
        mRenderTarget->AddRef();
    }

    if (mShaderResource)
    {
        mShaderResource->AddRef();
    }

    if (mBlitShaderResource)
    {
        mBlitShaderResource->AddRef();
    }

    if (mRenderTarget && mTexture)
    {
        mSubresourceIndex = getRTVSubresourceIndex(mTexture, mRenderTarget);
    }
    ASSERT(mANGLEFormat != d3d11::ANGLE_FORMAT_NONE || mWidth == 0 || mHeight == 0);
}

TextureRenderTarget11::TextureRenderTarget11(ID3D11DepthStencilView *dsv,
                                             ID3D11Resource *resource,
                                             ID3D11ShaderResourceView *srv,
                                             GLenum internalFormat,
                                             d3d11::ANGLEFormat angleFormat,
                                             GLsizei width,
                                             GLsizei height,
                                             GLsizei depth,
                                             GLsizei samples)
    : RenderTarget11(angleFormat),
      mWidth(width),
      mHeight(height),
      mDepth(depth),
      mInternalFormat(internalFormat),
      mSamples(samples),
      mSubresourceIndex(0),
      mTexture(resource),
      mRenderTarget(NULL),
      mDepthStencil(dsv),
      mShaderResource(srv),
      mBlitShaderResource(nullptr)
{
    if (mTexture)
    {
        mTexture->AddRef();
    }

    if (mDepthStencil)
    {
        mDepthStencil->AddRef();
    }

    if (mShaderResource)
    {
        mShaderResource->AddRef();
    }

    if (mDepthStencil && mTexture)
    {
        mSubresourceIndex = getDSVSubresourceIndex(mTexture, mDepthStencil);
    }
    ASSERT(mANGLEFormat != d3d11::ANGLE_FORMAT_NONE || mWidth == 0 || mHeight == 0);
}

TextureRenderTarget11::~TextureRenderTarget11()
{
    SafeRelease(mTexture);
    SafeRelease(mRenderTarget);
    SafeRelease(mDepthStencil);
    SafeRelease(mShaderResource);
    SafeRelease(mBlitShaderResource);
}

ID3D11Resource *TextureRenderTarget11::getTexture() const
{
    return mTexture;
}

ID3D11RenderTargetView *TextureRenderTarget11::getRenderTargetView() const
{
    return mRenderTarget;
}

ID3D11DepthStencilView *TextureRenderTarget11::getDepthStencilView() const
{
    return mDepthStencil;
}

ID3D11ShaderResourceView *TextureRenderTarget11::getShaderResourceView() const
{
    return mShaderResource;
}

ID3D11ShaderResourceView *TextureRenderTarget11::getBlitShaderResourceView() const
{
    return mBlitShaderResource;
}

GLsizei TextureRenderTarget11::getWidth() const
{
    return mWidth;
}

GLsizei TextureRenderTarget11::getHeight() const
{
    return mHeight;
}

GLsizei TextureRenderTarget11::getDepth() const
{
    return mDepth;
}

GLenum TextureRenderTarget11::getInternalFormat() const
{
    return mInternalFormat;
}

GLsizei TextureRenderTarget11::getSamples() const
{
    return mSamples;
}

unsigned int TextureRenderTarget11::getSubresourceIndex() const
{
    return mSubresourceIndex;
}

SurfaceRenderTarget11::SurfaceRenderTarget11(SwapChain11 *swapChain,
                                             Renderer11 *renderer,
                                             bool depth)
    : RenderTarget11(d3d11::ANGLE_FORMAT_NONE),  // format will be determined in constructor body
      mSwapChain(swapChain),
      mRenderer(renderer),
      mDepth(depth)
{
    ASSERT(mSwapChain);

    mANGLEFormat = d3d11::GetTextureFormatInfo(getInternalFormatInternal(),
                                               mRenderer->getRenderer11DeviceCaps())
                       .formatSet.format;
}

SurfaceRenderTarget11::~SurfaceRenderTarget11()
{
}

GLsizei SurfaceRenderTarget11::getWidth() const
{
    return mSwapChain->getWidth();
}

GLsizei SurfaceRenderTarget11::getHeight() const
{
    return mSwapChain->getHeight();
}

GLsizei SurfaceRenderTarget11::getDepth() const
{
    return 1;
}

GLenum SurfaceRenderTarget11::getInternalFormat() const
{
    return getInternalFormatInternal();
}

GLenum SurfaceRenderTarget11::getInternalFormatInternal() const
{
    return (mDepth ? mSwapChain->GetDepthBufferInternalFormat() : mSwapChain->GetRenderTargetInternalFormat());
}

GLsizei SurfaceRenderTarget11::getSamples() const
{
    // Our EGL surfaces do not support multisampling.
    return 0;
}

ID3D11Resource *SurfaceRenderTarget11::getTexture() const
{
    return (mDepth ? mSwapChain->getDepthStencilTexture() : mSwapChain->getOffscreenTexture());
}

ID3D11RenderTargetView *SurfaceRenderTarget11::getRenderTargetView() const
{
    return (mDepth ? NULL : mSwapChain->getRenderTarget());
}

ID3D11DepthStencilView *SurfaceRenderTarget11::getDepthStencilView() const
{
    return (mDepth ? mSwapChain->getDepthStencil() : NULL);
}

ID3D11ShaderResourceView *SurfaceRenderTarget11::getShaderResourceView() const
{
    return (mDepth ? mSwapChain->getDepthStencilShaderResource()
                   : mSwapChain->getRenderTargetShaderResource());
}

ID3D11ShaderResourceView *SurfaceRenderTarget11::getBlitShaderResourceView() const
{
    // The SurfaceRenderTargetView format should always be such that the normal SRV works for blits.
    return getShaderResourceView();
}

unsigned int SurfaceRenderTarget11::getSubresourceIndex() const
{
    return 0;
}

}
