#include "precompiled.h"
//
// Copyright (c) 2013 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.
//

// BufferStorage11.cpp Defines the BufferStorage11 class.

#include "libGLESv2/renderer/d3d11/BufferStorage11.h"
#include "libGLESv2/main.h"
#include "libGLESv2/renderer/d3d11/Renderer11.h"
#include "libGLESv2/renderer/d3d11/formatutils11.h"

namespace rx
{

BufferStorage11::BufferStorage11(Renderer11 *renderer)
{
    mRenderer = renderer;

    mStagingBuffer = NULL;
    mStagingBufferSize = 0;

    mSize = 0;

    mResolvedData = NULL;
    mResolvedDataSize = 0;
    mResolvedDataValid = false;

    mReadUsageCount = 0;
    mWriteUsageCount = 0;
}

BufferStorage11::~BufferStorage11()
{
    SafeRelease(mStagingBuffer);

    if (mResolvedData)
    {
        free(mResolvedData);
        mResolvedData = NULL;
    }

    for (size_t bufferIndex = 0; bufferIndex < mDirectBuffers.size(); bufferIndex++)
    {
        SafeDelete(mDirectBuffers[bufferIndex]);
    }
}

BufferStorage11 *BufferStorage11::makeBufferStorage11(BufferStorage *bufferStorage)
{
    ASSERT(HAS_DYNAMIC_TYPE(BufferStorage11*, bufferStorage));
    return static_cast<BufferStorage11*>(bufferStorage);
}

void *BufferStorage11::getData()
{
    ASSERT(mStagingBuffer);

    if (!mResolvedDataValid)
    {
        ID3D11Device *device = mRenderer->getDevice();
        ID3D11DeviceContext *context = mRenderer->getDeviceContext();
        HRESULT result;

        if (!mResolvedData || mResolvedDataSize < mStagingBufferSize)
        {
            free(mResolvedData);
            mResolvedData = malloc(mSize);
            mResolvedDataSize = mSize;
        }

        D3D11_MAPPED_SUBRESOURCE mappedResource;
        result = context->Map(mStagingBuffer, 0, D3D11_MAP_READ, 0, &mappedResource);
        if (FAILED(result))
        {
            return gl::error(GL_OUT_OF_MEMORY, (void*)NULL);
        }

        memcpy(mResolvedData, mappedResource.pData, mSize);

        context->Unmap(mStagingBuffer, 0);

        mResolvedDataValid = true;
    }

    mReadUsageCount = 0;

    return mResolvedData;
}

void BufferStorage11::setData(const void* data, unsigned int size, unsigned int offset)
{
    ID3D11Device *device = mRenderer->getDevice();
    ID3D11DeviceContext *context = mRenderer->getDeviceContext();
    HRESULT result;

    const unsigned int requiredStagingBufferSize = size + offset;
    const bool createStagingBuffer = !mStagingBuffer || mStagingBufferSize < requiredStagingBufferSize;

    if (createStagingBuffer)
    {
        D3D11_BUFFER_DESC bufferDesc;
        bufferDesc.ByteWidth = requiredStagingBufferSize;
        bufferDesc.Usage = D3D11_USAGE_STAGING;
        bufferDesc.BindFlags = 0;
        bufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
        bufferDesc.MiscFlags = 0;
        bufferDesc.StructureByteStride = 0;

        HRESULT result;
        ID3D11Device *device = mRenderer->getDevice();
        ID3D11DeviceContext *context = mRenderer->getDeviceContext();
        ID3D11Buffer *newStagingBuffer;

        if (data && offset == 0)
        {
            D3D11_SUBRESOURCE_DATA initialData;
            initialData.pSysMem = data;
            initialData.SysMemPitch = requiredStagingBufferSize;
            initialData.SysMemSlicePitch = 0;

            result = device->CreateBuffer(&bufferDesc, &initialData, &newStagingBuffer);
        }
        else
        {
            result = device->CreateBuffer(&bufferDesc, NULL, &newStagingBuffer);
        }

        if (FAILED(result))
        {
            mStagingBufferSize = 0;
            return gl::error(GL_OUT_OF_MEMORY);
        }

        mStagingBufferSize = requiredStagingBufferSize;

        if (mStagingBuffer && offset > 0)
        {
            // If offset is greater than zero and the buffer is non-null, need to preserve the data from
            // the old buffer up to offset
            D3D11_BOX srcBox;
            srcBox.left = 0;
            srcBox.right = std::min(offset, requiredStagingBufferSize);
            srcBox.top = 0;
            srcBox.bottom = 1;
            srcBox.front = 0;
            srcBox.back = 1;

            context->CopySubresourceRegion(newStagingBuffer, 0, 0, 0, 0, mStagingBuffer, 0, &srcBox);
        }

        SafeRelease(mStagingBuffer);
        mStagingBuffer = newStagingBuffer;
    }

    if (data && (offset != 0 || !createStagingBuffer))
    {
        D3D11_MAPPED_SUBRESOURCE mappedResource;
        result = context->Map(mStagingBuffer, 0, D3D11_MAP_WRITE, 0, &mappedResource);
        if (FAILED(result))
        {
            return gl::error(GL_OUT_OF_MEMORY);
        }

        unsigned char *offsetBufferPointer = reinterpret_cast<unsigned char *>(mappedResource.pData) + offset;
        memcpy(offsetBufferPointer, data, size);

        context->Unmap(mStagingBuffer, 0);
    }

    for (size_t bufferIndex = 0; bufferIndex < mDirectBuffers.size(); bufferIndex++)
    {
        mDirectBuffers[bufferIndex]->markDirty();
    }

    mSize = std::max(mSize, requiredStagingBufferSize);
    mWriteUsageCount = 0;

    mResolvedDataValid = false;
}

void BufferStorage11::copyData(BufferStorage* sourceStorage, unsigned int size,
                               unsigned int sourceOffset, unsigned int destOffset)
{
    BufferStorage11* source = makeBufferStorage11(sourceStorage);
    if (source)
    {
        ID3D11DeviceContext *context = mRenderer->getDeviceContext();

        D3D11_BOX srcBox;
        srcBox.left = sourceOffset;
        srcBox.right = sourceOffset + size;
        srcBox.top = 0;
        srcBox.bottom = 1;
        srcBox.front = 0;
        srcBox.back = 1;

        ASSERT(mStagingBuffer && source->mStagingBuffer);
        context->CopySubresourceRegion(mStagingBuffer, 0, destOffset, 0, 0, source->mStagingBuffer, 0, &srcBox);
    }
}

void BufferStorage11::clear()
{
    mResolvedDataValid = false;
    mSize = 0;
}

unsigned int BufferStorage11::getSize() const
{
    return mSize;
}

bool BufferStorage11::supportsDirectBinding() const
{
    return true;
}

void BufferStorage11::markBufferUsage()
{
    mReadUsageCount++;
    mWriteUsageCount++;

    const unsigned int usageLimit = 5;

    if (mReadUsageCount > usageLimit && mResolvedData)
    {
        free(mResolvedData);
        mResolvedData = NULL;
        mResolvedDataSize = 0;
        mResolvedDataValid = false;
    }
}

ID3D11Buffer *BufferStorage11::getBuffer(bool isConstantBufferUsage)
{
    markBufferUsage();

    for (size_t bufferIndex = 0; bufferIndex < mDirectBuffers.size(); bufferIndex++)
    {
        DirectBufferStorage11 *directBuffer = mDirectBuffers[bufferIndex];

        if (directBuffer->isConstantBufferUsage() == isConstantBufferUsage)
        {
            if (directBuffer->isDirty())
            {
                // if updateFromStagingBuffer returns true, the D3D buffer has been recreated
                // and we should update our serial
                if (directBuffer->updateFromStagingBuffer(mStagingBuffer, mSize, 0))
                {
                    updateSerial();
                }
            }
            return directBuffer->getD3DBuffer();
        }
    }

    // buffer is not allocated, create it
    DirectBufferStorage11 *directBuffer = new DirectBufferStorage11(mRenderer, isConstantBufferUsage);
    directBuffer->updateFromStagingBuffer(mStagingBuffer, mSize, 0);

    mDirectBuffers.push_back(directBuffer);
    updateSerial();

    return directBuffer->getD3DBuffer();
}

ID3D11ShaderResourceView *BufferStorage11::getSRV(DXGI_FORMAT srvFormat)
{
    ID3D11Buffer *buffer = getBuffer(false);

    auto bufferSRVIt = mBufferResourceViews.find(srvFormat);

    if (bufferSRVIt != mBufferResourceViews.end())
    {
        if (bufferSRVIt->second.first == buffer)
        {
            return bufferSRVIt->second.second;
        }
        else
        {
            // The underlying buffer has changed since the SRV was created: recreate the SRV.
            SafeRelease(bufferSRVIt->second.second);
        }
    }

    ID3D11Device *device = mRenderer->getDevice();
    ID3D11ShaderResourceView *bufferSRV = NULL;

    D3D11_SHADER_RESOURCE_VIEW_DESC bufferSRVDesc;
    bufferSRVDesc.Buffer.ElementOffset = 0;
    bufferSRVDesc.Buffer.ElementWidth = mSize / d3d11::GetFormatPixelBytes(srvFormat, mRenderer->getCurrentClientVersion());
    bufferSRVDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
    bufferSRVDesc.Format = srvFormat;

    HRESULT result = device->CreateShaderResourceView(buffer, &bufferSRVDesc, &bufferSRV);
    ASSERT(SUCCEEDED(result));

    mBufferResourceViews[srvFormat] = BufferSRVPair(buffer, bufferSRV);

    return bufferSRV;
}

DirectBufferStorage11::DirectBufferStorage11(Renderer11 *renderer, bool isConstantBufferUsage)
    : mRenderer(renderer),
      mIsConstantBufferUsage(isConstantBufferUsage),
      mDirectBuffer(NULL),
      mBufferSize(0),
      mDirty(false)
{
}

DirectBufferStorage11::~DirectBufferStorage11()
{
    SafeRelease(mDirectBuffer);
}

bool DirectBufferStorage11::isConstantBufferUsage() const
{
    return mIsConstantBufferUsage;
}

// Returns true if it recreates the direct buffer
bool DirectBufferStorage11::updateFromStagingBuffer(ID3D11Buffer *stagingBuffer, size_t size, size_t offset)
{
    ID3D11Device *device = mRenderer->getDevice();
    ID3D11DeviceContext *context = mRenderer->getDeviceContext();

    // unused for now
    ASSERT(offset == 0);

    unsigned int requiredBufferSize = size + offset;
    bool createBuffer = !mDirectBuffer || mBufferSize < requiredBufferSize;

    // (Re)initialize D3D buffer if needed
    if (createBuffer)
    {
        D3D11_BUFFER_DESC bufferDesc;
        fillBufferDesc(&bufferDesc, requiredBufferSize);

        ID3D11Buffer *newBuffer;
        HRESULT result = device->CreateBuffer(&bufferDesc, NULL, &newBuffer);

        if (FAILED(result))
        {
            return gl::error(GL_OUT_OF_MEMORY, false);
        }

        // No longer need the old buffer
        SafeRelease(mDirectBuffer);
        mDirectBuffer = newBuffer;

        mBufferSize = bufferDesc.ByteWidth;
    }

    // Copy data via staging buffer
    D3D11_BOX srcBox;
    srcBox.left = 0;
    srcBox.right = size;
    srcBox.top = 0;
    srcBox.bottom = 1;
    srcBox.front = 0;
    srcBox.back = 1;

    context->CopySubresourceRegion(mDirectBuffer, 0, offset, 0, 0, stagingBuffer, 0, &srcBox);

    mDirty = false;

    return createBuffer;
}

void DirectBufferStorage11::fillBufferDesc(D3D11_BUFFER_DESC* bufferDesc, unsigned int bufferSize)
{
    bufferDesc->ByteWidth = bufferSize;
    bufferDesc->MiscFlags = 0;
    bufferDesc->StructureByteStride = 0;

    if (!mIsConstantBufferUsage)
    {
        bufferDesc->Usage = D3D11_USAGE_DEFAULT;
        bufferDesc->BindFlags = D3D11_BIND_INDEX_BUFFER | D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_SHADER_RESOURCE;
        bufferDesc->CPUAccessFlags = 0;
    }
    else
    {
        bufferDesc->Usage = D3D11_USAGE_DYNAMIC;
        bufferDesc->BindFlags = D3D11_BIND_CONSTANT_BUFFER;
        bufferDesc->CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

        // Constant buffers must be of a limited size, and aligned to 16 byte boundaries
        // For our purposes we ignore any buffer data past the maximum constant buffer size
        bufferDesc->ByteWidth = roundUp(bufferDesc->ByteWidth, 16u);
        bufferDesc->ByteWidth = std::min(bufferDesc->ByteWidth, mRenderer->getMaxUniformBufferSize());
    }
}

}
