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

#ifndef GrEagerVertexAllocator_DEFINED
#define GrEagerVertexAllocator_DEFINED

#include "src/gpu/BufferWriter.h"
#include "src/gpu/ganesh/GrThreadSafeCache.h"

class GrMeshDrawTarget;

// This interface is used to allocate and map GPU vertex data before the exact number of required
// vertices is known. Usage pattern:
//
//   1. Call lock(eagerCount) with an upper bound on the number of required vertices.
//   2. Compute and write vertex data to the returned pointer (if not null).
//   3. Call unlock(actualCount) and provide the actual number of vertices written during step #2.
//
// On step #3, the implementation will attempt to shrink the underlying GPU memory slot to fit the
// actual vertex count.
class GrEagerVertexAllocator {
public:
    virtual void* lock(size_t stride, int eagerCount) = 0;

    virtual void unlock(int actualCount) = 0;

    virtual ~GrEagerVertexAllocator() {}

    skgpu::VertexWriter lockWriter(size_t stride, int eagerCount) {
        void* p = this->lock(stride, eagerCount);
        return p ? skgpu::VertexWriter{p, stride * eagerCount} : skgpu::VertexWriter{};
    }
};

// GrEagerVertexAllocator implementation that uses GrMeshDrawTarget::makeVertexSpace and
// GrMeshDrawTarget::putBackVertices.
class GrEagerDynamicVertexAllocator : public GrEagerVertexAllocator {
public:
    GrEagerDynamicVertexAllocator(GrMeshDrawTarget* target,
                                  sk_sp<const GrBuffer>* vertexBuffer,
                                  int* baseVertex)
            : fTarget(target)
            , fVertexBuffer(vertexBuffer)
            , fBaseVertex(baseVertex) {
    }

#ifdef SK_DEBUG
    ~GrEagerDynamicVertexAllocator() override {
        SkASSERT(!fLockCount);
    }
#endif

    // Mark "final" as a hint for the compiler to not use the vtable.
    void* lock(size_t stride, int eagerCount) final;

    // Mark "final" as a hint for the compiler to not use the vtable.
    void unlock(int actualCount) final;

private:
    GrMeshDrawTarget* const fTarget;
    sk_sp<const GrBuffer>* const fVertexBuffer;
    int* const fBaseVertex;

    size_t fLockStride;
    int fLockCount = 0;
};

class GrCpuVertexAllocator : public GrEagerVertexAllocator {
public:
    GrCpuVertexAllocator() = default;

#ifdef SK_DEBUG
    ~GrCpuVertexAllocator() override {
        SkASSERT(!fLockStride && !fVertices && !fVertexData);
    }
#endif

    void* lock(size_t stride, int eagerCount) override;
    void unlock(int actualCount) override;

    sk_sp<GrThreadSafeCache::VertexData> detachVertexData();

private:
    sk_sp<GrThreadSafeCache::VertexData> fVertexData;

    void*  fVertices = nullptr;
    size_t fLockStride = 0;
};

#endif
