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

// geometry/dx9.h: Direct3D 9-based implementation of BufferBackEnd, TranslatedVertexBuffer and TranslatedIndexBuffer.

#include "libGLESv2/geometry/dx9.h"

#include <cstddef>

#define GL_APICALL
#include <GLES2/gl2.h>

#include "common/debug.h"

#include "libGLESv2/Context.h"
#include "libGLESv2/geometry/vertexconversion.h"
#include "libGLESv2/geometry/IndexDataManager.h"

namespace
{
template <class InputType, template <std::size_t IncomingWidth> class WidenRule, class ElementConverter, class DefaultValueRule = gl::SimpleDefaultValues<InputType> >
class FormatConverterFactory
{
  private:
    template <std::size_t IncomingWidth>
    static gl::FormatConverter getFormatConverterForSize()
    {
        gl::FormatConverter formatConverter;

        formatConverter.identity = gl::VertexDataConverter<InputType, WidenRule<IncomingWidth>, ElementConverter, DefaultValueRule>::identity;
        formatConverter.outputVertexSize = gl::VertexDataConverter<InputType, WidenRule<IncomingWidth>, ElementConverter, DefaultValueRule>::finalSize;
        formatConverter.convertArray = gl::VertexDataConverter<InputType, WidenRule<IncomingWidth>, ElementConverter, DefaultValueRule>::convertArray;

        return formatConverter;
    }

  public:
    static gl::FormatConverter getFormatConverter(std::size_t size)
    {
        switch (size)
        {
          case 1: return getFormatConverterForSize<1>();
          case 2: return getFormatConverterForSize<2>();
          case 3: return getFormatConverterForSize<3>();
          case 4: return getFormatConverterForSize<4>();
          default: UNREACHABLE(); return getFormatConverterForSize<1>();
        }
    }
};
}

namespace gl
{
Dx9BackEnd::Dx9BackEnd(IDirect3DDevice9 *d3ddevice)
    : mDevice(d3ddevice)
{
    mDevice->AddRef();
}

Dx9BackEnd::~Dx9BackEnd()
{
    mDevice->Release();
}

bool Dx9BackEnd::supportIntIndices()
{
    D3DCAPS9 caps;
    mDevice->GetDeviceCaps(&caps);

    return (caps.MaxVertexIndex >= (1 << 16));
}

TranslatedVertexBuffer *Dx9BackEnd::createVertexBuffer(std::size_t size)
{
    return new Dx9VertexBuffer(mDevice, size);
}

TranslatedVertexBuffer *Dx9BackEnd::createVertexBufferForStrideZero(std::size_t size)
{
    return new Dx9VertexBufferZeroStrideWorkaround(mDevice, size);
}

TranslatedIndexBuffer *Dx9BackEnd::createIndexBuffer(std::size_t size, GLenum type)
{
    return new Dx9IndexBuffer(mDevice, size, type);
}

// Mapping from OpenGL-ES vertex attrib type to D3D decl type:
//
// BYTE                 Translate to SHORT, expand to x2,x4 as needed.
// BYTE-norm            Translate to FLOAT since it can't be exactly represented as SHORT-norm.
// UNSIGNED_BYTE        x4 only. x1,x2,x3=>x4
// UNSIGNED_BYTE-norm   x4 only, x1,x2,x3=>x4
// SHORT                x2,x4 supported. x1=>x2, x3=>x4
// SHORT-norm           x2,x4 supported. x1=>x2, x3=>x4
// UNSIGNED_SHORT       unsupported, translate to float
// UNSIGNED_SHORT-norm  x2,x4 supported. x1=>x2, x3=>x4
// FIXED (not in WebGL) Translate to float.
// FLOAT                Fully supported.

FormatConverter Dx9BackEnd::getFormatConverter(GLenum type, std::size_t size, bool normalize)
{
    // FIXME: This should be rewritten to use C99 exact-sized types.
    switch (type)
    {
      case GL_BYTE:
        if (normalize)
        {
            return FormatConverterFactory<char, NoWiden, Normalize<char> >::getFormatConverter(size);
        }
        else
        {
            return FormatConverterFactory<char, WidenToEven, Cast<char, short> >::getFormatConverter(size);
        }

      case GL_UNSIGNED_BYTE:
        if (normalize)
        {
            return FormatConverterFactory<unsigned char, WidenToFour, Identity<unsigned char>, NormalizedDefaultValues<unsigned char> >::getFormatConverter(size);
        }
        else
        {
            return FormatConverterFactory<unsigned char, WidenToFour, Identity<unsigned char> >::getFormatConverter(size);
        }

      case GL_SHORT:
        if (normalize)
        {
            return FormatConverterFactory<short, WidenToEven, Identity<short>, NormalizedDefaultValues<short> >::getFormatConverter(size);
        }
        else
        {
            return FormatConverterFactory<short, WidenToEven, Identity<short> >::getFormatConverter(size);
        }

      case GL_UNSIGNED_SHORT:
        if (normalize)
        {
            return FormatConverterFactory<unsigned short, WidenToEven, Identity<unsigned short>, NormalizedDefaultValues<unsigned short> >::getFormatConverter(size);
        }
        else
        {
            return FormatConverterFactory<unsigned short, NoWiden, Cast<unsigned short, float> >::getFormatConverter(size);
        }

      case GL_FIXED:
        return FormatConverterFactory<int, NoWiden, FixedToFloat<int, 16> >::getFormatConverter(size);

      case GL_FLOAT:
        return FormatConverterFactory<float, NoWiden, Identity<float> >::getFormatConverter(size);

      default: UNREACHABLE(); return FormatConverterFactory<float, NoWiden, Identity<float> >::getFormatConverter(1);
    }
}

D3DDECLTYPE Dx9BackEnd::mapAttributeType(GLenum type, std::size_t size, bool normalized) const
{
    static const D3DDECLTYPE byteTypes[2][4] = { { D3DDECLTYPE_SHORT2, D3DDECLTYPE_SHORT2, D3DDECLTYPE_SHORT4, D3DDECLTYPE_SHORT4 }, { D3DDECLTYPE_FLOAT1, D3DDECLTYPE_FLOAT2, D3DDECLTYPE_FLOAT3, D3DDECLTYPE_FLOAT4 } };
    static const D3DDECLTYPE shortTypes[2][4] = { { D3DDECLTYPE_SHORT2, D3DDECLTYPE_SHORT2, D3DDECLTYPE_SHORT4, D3DDECLTYPE_SHORT4 }, { D3DDECLTYPE_SHORT2N, D3DDECLTYPE_SHORT2N, D3DDECLTYPE_SHORT4N, D3DDECLTYPE_SHORT4N } };
    static const D3DDECLTYPE ushortTypes[2][4] = { { D3DDECLTYPE_FLOAT1, D3DDECLTYPE_FLOAT2, D3DDECLTYPE_FLOAT3, D3DDECLTYPE_FLOAT4 }, { D3DDECLTYPE_USHORT2N, D3DDECLTYPE_USHORT2N, D3DDECLTYPE_USHORT4N, D3DDECLTYPE_USHORT4N } };

    static const D3DDECLTYPE floatTypes[4] =  { D3DDECLTYPE_FLOAT1, D3DDECLTYPE_FLOAT2, D3DDECLTYPE_FLOAT3, D3DDECLTYPE_FLOAT4 };

    switch (type)
    {
      case GL_BYTE: return byteTypes[normalized][size-1];
      case GL_UNSIGNED_BYTE: return normalized ? D3DDECLTYPE_UBYTE4N : D3DDECLTYPE_UBYTE4;
      case GL_SHORT: return shortTypes[normalized][size-1];
      case GL_UNSIGNED_SHORT: return ushortTypes[normalized][size-1];

      case GL_FIXED:
      case GL_FLOAT:
        return floatTypes[size-1];

      default:
        UNREACHABLE();
        return D3DDECLTYPE_FLOAT1;
    }
}

bool Dx9BackEnd::validateStream(GLenum type, std::size_t size, std::size_t stride, std::size_t offset) const
{
    // D3D9 requires the stream offset and stride to be a multiple of DWORD.
    return (stride % sizeof(DWORD) == 0 && offset % sizeof(DWORD) == 0);
}

IDirect3DVertexBuffer9 *Dx9BackEnd::getDxBuffer(TranslatedVertexBuffer *vb) const
{
    return vb ? static_cast<Dx9VertexBuffer*>(vb)->getBuffer() : NULL;
}

IDirect3DIndexBuffer9 *Dx9BackEnd::getDxBuffer(TranslatedIndexBuffer *ib) const
{
    return ib ? static_cast<Dx9IndexBuffer*>(ib)->getBuffer() : NULL;
}

GLenum Dx9BackEnd::setupIndicesPreDraw(const TranslatedIndexData &indexInfo)
{
    mDevice->SetIndices(getDxBuffer(indexInfo.buffer));
    return GL_NO_ERROR;
}

GLenum Dx9BackEnd::setupAttributesPreDraw(const TranslatedAttribute *attributes)
{
    HRESULT hr;

    D3DVERTEXELEMENT9 elements[MAX_VERTEX_ATTRIBS+1];

    D3DVERTEXELEMENT9 *nextElement = &elements[0];

    for (BYTE i = 0; i < MAX_VERTEX_ATTRIBS; i++)
    {
        if (attributes[i].enabled)
        {
            nextElement->Stream = i;
            nextElement->Offset = 0;
            nextElement->Type = static_cast<BYTE>(mapAttributeType(attributes[i].type, attributes[i].size, attributes[i].normalized));
            nextElement->Method = D3DDECLMETHOD_DEFAULT;
            nextElement->Usage = D3DDECLUSAGE_TEXCOORD;
            nextElement->UsageIndex = attributes[i].semanticIndex;
            nextElement++;
        }
    }

    static const D3DVERTEXELEMENT9 end = D3DDECL_END();
    *nextElement = end;

    IDirect3DVertexDeclaration9* vertexDeclaration;
    hr = mDevice->CreateVertexDeclaration(elements, &vertexDeclaration);
    mDevice->SetVertexDeclaration(vertexDeclaration);
    vertexDeclaration->Release();

    for (size_t i = 0; i < MAX_VERTEX_ATTRIBS; i++)
    {
        if (attributes[i].enabled)
        {
            mDevice->SetStreamSource(i, getDxBuffer(attributes[i].buffer), attributes[i].offset, attributes[i].stride);
        }
        else
        {
            mDevice->SetStreamSource(i, 0, 0, 0);
        }
    }

    return GL_NO_ERROR;
}

Dx9BackEnd::Dx9VertexBuffer::Dx9VertexBuffer(IDirect3DDevice9 *device, std::size_t size)
    : TranslatedVertexBuffer(size)
{
    HRESULT hr = device->CreateVertexBuffer(size, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, &mVertexBuffer, NULL);
    if (hr != S_OK)
    {
        ERR("Out of memory allocating a vertex buffer of size %lu.", size);
        throw std::bad_alloc();
    }
}

Dx9BackEnd::Dx9VertexBuffer::Dx9VertexBuffer(IDirect3DDevice9 *device, std::size_t size, DWORD usageFlags)
    : TranslatedVertexBuffer(size)
{
    HRESULT hr = device->CreateVertexBuffer(size, usageFlags, 0, D3DPOOL_DEFAULT, &mVertexBuffer, NULL);
    if (hr != S_OK)
    {
        ERR("Out of memory allocating a vertex buffer of size %lu.", size);
        throw std::bad_alloc();
    }
}


Dx9BackEnd::Dx9VertexBuffer::~Dx9VertexBuffer()
{
    mVertexBuffer->Release();
}

IDirect3DVertexBuffer9 *Dx9BackEnd::Dx9VertexBuffer::getBuffer() const
{
    return mVertexBuffer;
}

void *Dx9BackEnd::Dx9VertexBuffer::map()
{
    void *mapPtr;

    mVertexBuffer->Lock(0, 0, &mapPtr, 0);

    return mapPtr;
}

void Dx9BackEnd::Dx9VertexBuffer::unmap()
{
    mVertexBuffer->Unlock();
}

void Dx9BackEnd::Dx9VertexBuffer::recycle()
{
    void *dummy;
    mVertexBuffer->Lock(0, 1, &dummy, D3DLOCK_DISCARD);
    mVertexBuffer->Unlock();
}

void *Dx9BackEnd::Dx9VertexBuffer::streamingMap(std::size_t offset, std::size_t size)
{
    void *mapPtr;

    mVertexBuffer->Lock(offset, size, &mapPtr, D3DLOCK_NOOVERWRITE);

    return mapPtr;
}

// Normally VBs are created with D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, but some hardware & drivers won't render
// if any stride-zero streams are in D3DUSAGE_DYNAMIC VBs, so this provides a way to create such VBs with only D3DUSAGE_WRITEONLY set.
// D3DLOCK_DISCARD and D3DLOCK_NOOVERWRITE are only available on D3DUSAGE_DYNAMIC VBs, so we override methods to avoid using these flags.
Dx9BackEnd::Dx9VertexBufferZeroStrideWorkaround::Dx9VertexBufferZeroStrideWorkaround(IDirect3DDevice9 *device, std::size_t size)
    : Dx9VertexBuffer(device, size, D3DUSAGE_WRITEONLY)
{
}

void Dx9BackEnd::Dx9VertexBufferZeroStrideWorkaround::recycle()
{
}

void *Dx9BackEnd::Dx9VertexBufferZeroStrideWorkaround::streamingMap(std::size_t offset, std::size_t size)
{
    void *mapPtr;

    getBuffer()->Lock(offset, size, &mapPtr, 0);

    return mapPtr;
}

Dx9BackEnd::Dx9IndexBuffer::Dx9IndexBuffer(IDirect3DDevice9 *device, std::size_t size, GLenum type)
    : TranslatedIndexBuffer(size)
{
    ASSERT(type == GL_UNSIGNED_SHORT || type == GL_UNSIGNED_INT);

    D3DFORMAT format = (type == GL_UNSIGNED_SHORT) ? D3DFMT_INDEX16 : D3DFMT_INDEX32;

    HRESULT hr = device->CreateIndexBuffer(size, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, format, D3DPOOL_DEFAULT, &mIndexBuffer, NULL);
    if (hr != S_OK)
    {
        ERR("Out of memory allocating an index buffer of size %lu.", size);
        throw std::bad_alloc();
    }
}

Dx9BackEnd::Dx9IndexBuffer::~Dx9IndexBuffer()
{
    mIndexBuffer->Release();
}

IDirect3DIndexBuffer9*Dx9BackEnd::Dx9IndexBuffer::getBuffer() const
{
    return mIndexBuffer;
}

void *Dx9BackEnd::Dx9IndexBuffer::map()
{
    void *mapPtr;

    mIndexBuffer->Lock(0, 0, &mapPtr, 0);

    return mapPtr;
}

void Dx9BackEnd::Dx9IndexBuffer::unmap()
{
    mIndexBuffer->Unlock();
}

void Dx9BackEnd::Dx9IndexBuffer::recycle()
{
    void *dummy;
    mIndexBuffer->Lock(0, 1, &dummy, D3DLOCK_DISCARD);
    mIndexBuffer->Unlock();
}

void *Dx9BackEnd::Dx9IndexBuffer::streamingMap(std::size_t offset, std::size_t size)
{
    void *mapPtr;

    mIndexBuffer->Lock(offset, size, &mapPtr, D3DLOCK_NOOVERWRITE);

    return mapPtr;
}

}
