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

#ifndef GrProgramDesc_DEFINED
#define GrProgramDesc_DEFINED

#include "include/core/SkString.h"
#include "include/private/GrTypesPriv.h"
#include "include/private/SkTArray.h"
#include "include/private/SkTo.h"

#include <limits.h>

class GrCaps;
class GrProgramInfo;
class GrRenderTarget;
class GrShaderCaps;

class GrProcessorKeyBuilder {
public:
    GrProcessorKeyBuilder(SkTArray<uint32_t, true>* data) : fData(data) {}

    virtual ~GrProcessorKeyBuilder() {
        // Ensure that flush was called before we went out of scope
        SkASSERT(fBitsUsed == 0);
    }

    virtual void addBits(uint32_t numBits, uint32_t val, const char* label) {
        SkASSERT(numBits > 0 && numBits <= 32);
        SkASSERT(numBits == 32 || (val < (1u << numBits)));

        fCurValue |= (val << fBitsUsed);
        fBitsUsed += numBits;

        if (fBitsUsed >= 32) {
            // Overflow, start a new working value
            fData->push_back(fCurValue);
            uint32_t excess = fBitsUsed - 32;
            fCurValue = excess ? (val >> (numBits - excess)) : 0;
            fBitsUsed = excess;
        }

        SkASSERT(fCurValue < (1u << fBitsUsed));
    }

    void addBytes(uint32_t numBytes, const void* data, const char* label) {
        const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data);
        for (; numBytes --> 0; bytes++) {
            this->addBits(8, *bytes, label);
        }
    }

    void addBool(bool b, const char* label) {
        this->addBits(1, b, label);
    }

    void add32(uint32_t v, const char* label = "unknown") {
        this->addBits(32, v, label);
    }

    virtual void appendComment(const char* comment) {}

    // Introduces a word-boundary in the key. Must be called before using the key with any cache,
    // but can also be called to create a break between generic data and backend-specific data.
    void flush() {
        if (fBitsUsed) {
            fData->push_back(fCurValue);
            fCurValue = 0;
            fBitsUsed = 0;
        }
    }

private:
    SkTArray<uint32_t, true>* fData;
    uint32_t fCurValue = 0;
    uint32_t fBitsUsed = 0;  // ... in current value
};

class GrProcessorStringKeyBuilder : public GrProcessorKeyBuilder {
public:
    GrProcessorStringKeyBuilder(SkTArray<uint32_t, true>* data) : INHERITED(data) {}

    void addBits(uint32_t numBits, uint32_t val, const char* label) override {
        INHERITED::addBits(numBits, val, label);
        fDescription.appendf("%s: %u\n", label, val);
    }

    void appendComment(const char* comment) override {
        fDescription.appendf("%s\n", comment);
    }

    SkString description() const { return fDescription; }

private:
    using INHERITED = GrProcessorKeyBuilder;
    SkString fDescription;
};

/** This class is used to generate a generic program cache key. The Dawn, Metal and Vulkan
 *  backends derive backend-specific versions which add additional information.
 */
class GrProgramDesc {
public:
    GrProgramDesc(const GrProgramDesc& other) = default;

    bool isValid() const { return !fKey.empty(); }
    void reset() { *this = GrProgramDesc{}; }

    // Returns this as a uint32_t array to be used as a key in the program cache.
    const uint32_t* asKey() const {
        return fKey.data();
    }

    // Gets the number of bytes in asKey(). It will be a 4-byte aligned value.
    uint32_t keyLength() const {
        return fKey.size() * sizeof(uint32_t);
    }

    bool operator== (const GrProgramDesc& that) const {
        return this->fKey == that.fKey;
    }

    bool operator!= (const GrProgramDesc& other) const {
        return !(*this == other);
    }

    uint32_t initialKeyLength() const { return fInitialKeyLength; }

    // TODO(skia:11372): Incorporate this into caps interface (part of makeDesc, or a parallel
    // function), so other backends can include their information in the description.
    static SkString Describe(const GrProgramInfo&, const GrCaps&);

protected:
    friend class GrDawnCaps;
    friend class GrD3DCaps;
    friend class GrGLCaps;
    friend class GrMockCaps;
    friend class GrMtlCaps;
    friend class GrVkCaps;

    friend class GrGLGpu; // for ProgramCache to access BuildFromData
    friend class GrMtlResourceProvider; // for PipelineStateCache to access BuildFromData

    // Creates an uninitialized key that must be populated by Build
    GrProgramDesc() {}

    /**
     * Builds a program descriptor.
     *
     * @param desc          The built descriptor
     * @param programInfo   Program information need to build the key
     * @param caps          the caps
     **/
    static void Build(GrProgramDesc*, const GrProgramInfo&, const GrCaps&);

    // This is strictly an OpenGL call since the other backends have additional data in their keys.
    static bool BuildFromData(GrProgramDesc* desc, const void* keyData, size_t keyLength) {
        if (!SkTFitsIn<int>(keyLength) || !SkIsAlign4(keyLength)) {
            return false;
        }
        desc->fKey.reset(keyLength / 4);
        memcpy(desc->fKey.begin(), keyData, keyLength);
        return true;
    }

    enum {
        kHeaderSize            = 1,    // "header" in ::Build
        kMaxPreallocProcessors = 8,
        kIntsPerProcessor      = 4,    // This is an overestimate of the average effect key size.
        kPreAllocSize = kHeaderSize +
                        kMaxPreallocProcessors * kIntsPerProcessor,
    };

    using KeyType = SkSTArray<kPreAllocSize, uint32_t, true>;

    KeyType* key() { return &fKey; }

private:
    SkSTArray<kPreAllocSize, uint32_t, true> fKey;
    uint32_t fInitialKeyLength = 0;
};

#endif
