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

#ifndef SortKey_DEFINED
#define SortKey_DEFINED

#include "include/core/SkTypes.h"

// These are the material IDs that are stored in the sort key for each class of material
constexpr int kInvalidMat = 0;
constexpr int kSolidMat   = 1;
constexpr int kLinearMat  = 2;
constexpr int kRadialMat  = 3;

class SortKey {
public:
    // field:  |  transparent  | clipID  | depth   |  material  |
    // bits:   |      1        |   8     |  4      |     4      |
    // Note: the depth and material fields are swapped when the key is opaque and the depth's
    // order is reversed. This forces all opaque draws with the be sorted by material first
    // and then front to back. Transparent draws will continue to be sorted back to front.
    static const uint32_t kMaterialShift = 0;
    static const uint32_t kNumMaterialBits = 4;
    static const uint32_t kMaterialMask = (0x1 << kNumMaterialBits) - 1;

    // The pseudo-Z generated by the draw calls is just a proxy for painter's order.
    // The "depth" value stored here is a munged version of the pseudo-Z to get the sorting
    // correct.
    // For opaque objects the pseudo-Z is reversed so opaque objects are drawn front to back (i.e.,
    // reverse painter's order).
    // For transparent objects the pseudo-Z is untouched but the transparent bit is set on the
    // key so transparent object will always be drawn after the opaque objects and in painter's
    // order.
    static const uint32_t kDepthShift = kNumMaterialBits;
    static const uint32_t kNumDepthBits = 4;
    static const uint32_t kDepthMask = (0x1 << kNumDepthBits) - 1;
    static const uint32_t kMaxDepth = kDepthMask;

    static const uint32_t kTransparentShift = kNumMaterialBits + kNumDepthBits;
    static const uint32_t kNumTransparentBits = 1;
    static const uint32_t kTransparentMask = (0x1 << kNumTransparentBits) - 1;

    // TODO: make it clearer that we're initializing the default depth to be 0 here (since the
    // default key is opaque, its sense is flipped)
    SortKey() : fKey((kMaxDepth - 1) << kMaterialShift) {}
    explicit SortKey(bool transparent, uint32_t depth, uint32_t material) {
        SkASSERT(depth != 0 /* && material != 0*/);
        SkASSERT(!(depth & ~kDepthMask));
        SkASSERT(!(material & ~kMaterialMask));

        // TODO: better encapsulate the reversal of the depth & material when the key is opaque
        if (transparent) {
            fKey = (0x1 << kTransparentShift) |
                   (depth & kDepthMask) << kDepthShift |
                   (material & kMaterialMask) << kMaterialShift;
        } else {
            SkASSERT(kNumDepthBits == kNumMaterialBits);

            uint32_t munged;
            // We want the opaque draws to be sorted front to back
            munged = kMaxDepth - depth - 1;
            SkASSERT(!(munged & ~kDepthMask));

            fKey = (munged & kDepthMask) << kMaterialShift |
                   (material & kMaterialMask) << kDepthShift;
        }
    }

    bool transparent() const {
        return (fKey >> kTransparentShift) & kTransparentMask;
    }

    uint32_t depth() const {
        if (this->transparent()) {
            return (fKey >> kDepthShift) & kDepthMask;
        }

        // TODO: better encapsulate the reversal of the depth & material when the key is opaque
        uint32_t tmp = (fKey >> kMaterialShift) & kDepthMask;
        return (kMaxDepth - tmp) - 1;
    }

    uint32_t material() const {
        // TODO: better encapsulate the reversal of the depth & material when the key is opaque
        if (this->transparent()) {
            return (fKey >> kMaterialShift) & kMaterialMask;
        } else {
            return (fKey >> kDepthShift) & kMaterialMask;
        }
    }

    void dump() const {
        SkDebugf("transparent: %d depth: %d mat: %d\n",
                 this->transparent(),
                 this->depth(),
                 this->material());
    }

    bool operator>(const SortKey& other) const { return fKey > other.fKey; }
    bool operator<(const SortKey& other) const { return fKey < other.fKey; }

private:
    uint64_t fKey;
};

#endif // Key_DEFINED
