/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/

#include "GrVkUniformBuffer.h"
#include "GrVkGpu.h"

#define VK_CALL(GPU, X) GR_VK_CALL(GPU->vkInterface(), X)

GrVkUniformBuffer* GrVkUniformBuffer::Create(GrVkGpu* gpu, size_t size) {
    if (0 == size) {
        return nullptr;
    }
    const GrVkResource* resource = nullptr;
    if (size <= GrVkUniformBuffer::kStandardSize) {
        resource = gpu->resourceProvider().findOrCreateStandardUniformBufferResource();
    } else {
        resource = CreateResource(gpu, size);
    }
    if (!resource) {
        return nullptr;
    }

    GrVkBuffer::Desc desc;
    desc.fDynamic = true;
    desc.fType = GrVkBuffer::kUniform_Type;
    desc.fSizeInBytes = size;
    GrVkUniformBuffer* buffer = new GrVkUniformBuffer(gpu, desc,
                                                      (const GrVkUniformBuffer::Resource*) resource);
    if (!buffer) {
        // this will destroy anything we got from the resource provider,
        // but this avoids a conditional
        resource->unref(gpu);
    }
    return buffer;
}

// We implement our own creation function for special buffer resource type
const GrVkResource* GrVkUniformBuffer::CreateResource(GrVkGpu* gpu, size_t size) {
    if (0 == size) {
        return nullptr;
    }

    VkBuffer       buffer;
    GrVkAlloc      alloc;

    // create the buffer object
    VkBufferCreateInfo bufInfo;
    memset(&bufInfo, 0, sizeof(VkBufferCreateInfo));
    bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufInfo.flags = 0;
    bufInfo.size = size;
    bufInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
    bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    bufInfo.queueFamilyIndexCount = 0;
    bufInfo.pQueueFamilyIndices = nullptr;

    VkResult err;
    err = VK_CALL(gpu, CreateBuffer(gpu->device(), &bufInfo, nullptr, &buffer));
    if (err) {
        return nullptr;
    }

    if (!GrVkMemory::AllocAndBindBufferMemory(gpu,
                                              buffer,
                                              kUniform_Type,
                                              true,  // dynamic
                                              &alloc)) {
        return nullptr;
    }

    const GrVkResource* resource = new GrVkUniformBuffer::Resource(buffer, alloc);
    if (!resource) {
        VK_CALL(gpu, DestroyBuffer(gpu->device(), buffer, nullptr));
        GrVkMemory::FreeBufferMemory(gpu, kUniform_Type, alloc);
        return nullptr;
    }

    return resource;
}

const GrVkBuffer::Resource* GrVkUniformBuffer::createResource(GrVkGpu* gpu,
                                                              const GrVkBuffer::Desc& descriptor) {
    const GrVkResource* vkResource;
    if (descriptor.fSizeInBytes <= GrVkUniformBuffer::kStandardSize) {
        GrVkResourceProvider& provider = gpu->resourceProvider();
        vkResource = provider.findOrCreateStandardUniformBufferResource();
    } else {
        vkResource = CreateResource(gpu, descriptor.fSizeInBytes);
    }
    return (const GrVkBuffer::Resource*) vkResource;
}

void GrVkUniformBuffer::Resource::onRecycle(GrVkGpu* gpu) const {
    if (fAlloc.fSize <= GrVkUniformBuffer::kStandardSize) {
        gpu->resourceProvider().recycleStandardUniformBufferResource(this);
    } else {
        this->unref(gpu);
    }
}