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

#include "src/gpu/graphite/UniformManager.h"

#include "src/gpu/graphite/PipelineData.h"

// ensure that these types are the sizes the uniform data is expecting
static_assert(sizeof(int32_t) == 4);
static_assert(sizeof(float) == 4);
static_assert(sizeof(SkHalf) == 2);

namespace skgpu::graphite {

int UniformOffsetCalculator::advanceOffset(SkSLType type, int count) {
    SkASSERT(SkSLTypeCanBeUniformValue(type));

    int dimension = SkSLTypeMatrixSize(type);
    if (dimension > 0) {
        // All SkSL matrices are square and can be interpreted as an array of column vectors
        count = std::max(count, 1) * dimension;
    } else {
        dimension = SkSLTypeVecLength(type);
    }
    SkASSERT(1 <= dimension && dimension <= 4);

    // Bump dimension up to 4 if the array or vec3 consumes 4 primitives per element
    // NOTE: This affects the size, alignment already rounds up to a power of 2 automatically.
    const bool isArray = count > Uniform::kNonArray;
    const bool forceAlign16 = isArray && LayoutRules::AlignArraysTo16(fLayout);
    if (forceAlign16 || (dimension == 3 && (isArray || LayoutRules::PadVec3Size(fLayout)))) {
        dimension = 4;
    }

    const int primitiveSize = LayoutRules::UseFullPrecision(fLayout) ||
                              SkSLTypeIsFullPrecisionNumericType(type) ||
                              forceAlign16 ? 4 : 2;
    const int align = SkNextPow2(dimension) * primitiveSize;
    const int alignedOffset = SkAlignTo(fOffset, align);
    fOffset = alignedOffset + dimension * primitiveSize * std::max(count, 1);
    fReqAlignment = std::max(fReqAlignment, align);

    return alignedOffset;
}

int UniformOffsetCalculator::advanceStruct(const UniformOffsetCalculator& substruct, int count) {
    SkASSERT(substruct.fLayout == fLayout); // Invalid if the layout rules used aren't consistent

    // If array element strides are forced to 16-byte alignment, structs must also have their
    // base alignment rounded up to 16-byte alignment, which should have been accounted for in
    // 'substruct's constructor.
    const int baseAlignment = substruct.requiredAlignment();
    SkASSERT(!LayoutRules::AlignArraysTo16(fLayout) || SkIsAlign16(baseAlignment));

    // Per layout rule #9, the struct size must be padded to its base alignment
    // (see https://registry.khronos.org/OpenGL/specs/gl/glspec45.core.pdf#page=159).
    const int alignedSize = SkAlignTo(substruct.size(), baseAlignment);

    const int alignedOffset = SkAlignTo(fOffset, baseAlignment);
    fOffset = alignedOffset + alignedSize * std::max(count, 1);
    fReqAlignment = std::max(fReqAlignment, baseAlignment);

    return alignedOffset;
}

//////////////////////////////////////////////////////////////////////////////

void UniformManager::resetWithNewLayout(Layout layout) {
    fStorage.clear();
    fLayout = layout;
    fReqAlignment = 1;
    fEndPaintAlignment = 1;
    fEndPaintOffset = 0;
    fNonShadingOffset = 0;
    fStructBaseAlignment = 0;
    fWrotePaintColor = false;

#ifdef SK_DEBUG
    fOffsetCalculator = UniformOffsetCalculator::ForTopLevel(layout);
    fMarkedOffsetCalculator = fOffsetCalculator;
    fSubstructCalculator = {};
    fExpectedUniforms = {};
    fExpectedUniformIndex = 0;
#endif
}

void UniformManager::rewindToMark() {
    // Prepare the storage paramters such that:
    //  1) If the renderstep is shading, the size and alignment can grow directly from the state at
    //  at the end of the paint uniforms.
    //  2) If the renderstep is non-shading, the storage can be aligned to the renderstep's
    //  uniform alignment requirements.
    fStorage.resize(fEndPaintOffset);
    fReqAlignment = fEndPaintAlignment;
    fNonShadingOffset = 0;
    SkDEBUGCODE(fOffsetCalculator = fMarkedOffsetCalculator);

    // If we're rewinding, we shouldn't be using substructs.
    SkASSERT(fSubstructStartingOffset == -1);
    // Any struct should be closed.
    SkASSERT(fStructBaseAlignment == 0);
}

static std::pair<SkSLType, int> adjust_for_matrix_type(SkSLType type, int count) {
    // All Layouts flatten matrices and arrays of matrices into arrays of columns, so update
    // 'type' to be the column type and either multiply 'count' by the number of columns for
    // arrays of matrices, or set to exactly the number of columns for a "non-array" matrix.
    switch(type) {
        case SkSLType::kFloat2x2: return {SkSLType::kFloat2, 2*std::max(1, count)};
        case SkSLType::kFloat3x3: return {SkSLType::kFloat3, 3*std::max(1, count)};
        case SkSLType::kFloat4x4: return {SkSLType::kFloat4, 4*std::max(1, count)};

        case SkSLType::kHalf2x2:  return {SkSLType::kHalf2,  2*std::max(1, count)};
        case SkSLType::kHalf3x3:  return {SkSLType::kHalf3,  3*std::max(1, count)};
        case SkSLType::kHalf4x4:  return {SkSLType::kHalf4,  4*std::max(1, count)};

        // Otherwise leave type and count alone.
        default:                  return {type, count};
    }
}

void UniformManager::write(const Uniform& u, const void* data) {
    SkASSERT(SkSLTypeCanBeUniformValue(u.type()));
    SkASSERT(!u.isPaintColor()); // Must go through writePaintColor()

    auto [type, count] = adjust_for_matrix_type(u.type(), u.count());
    SkASSERT(SkSLTypeMatrixSize(type) < 0); // Matrix types should have been flattened

    const bool fullPrecision = LayoutRules::UseFullPrecision(fLayout) || !IsHalfVector(type);

#define GEN_WRITE_TABLE(M, Half, Align16) \
    switch(SkSLTypeVecLength(type)) { \
        case 1: M(1, Half, Align16); break; \
        case 2: M(2, Half, Align16); break; \
        case 3: M(3, Half, Align16); break; \
        case 4: M(4, Half, Align16); break; }
#define FOR_NON_ARRAY(N, Half, Align16) this->write<N, Half>(data, type)
#define FOR_ARRAY(N, Half, Align16) this->writeArray<N, Half, Align16>(data, count, type)

    if (count == Uniform::kNonArray) {
        if (fullPrecision) {
            GEN_WRITE_TABLE(FOR_NON_ARRAY, /*Half=*/false, /*ignored*/)
        } else {
            GEN_WRITE_TABLE(FOR_NON_ARRAY, /*Half=*/true,  /*ignored*/)
        }
    } else {
        if (LayoutRules::AlignArraysTo16(fLayout)) {
            if (fullPrecision) {
                GEN_WRITE_TABLE(FOR_ARRAY, /*Half=*/false, /*Align16=*/true)
            } else {
                GEN_WRITE_TABLE(FOR_ARRAY, /*Half=*/true,  /*Align16=*/true)
            }
        } else {
            if (fullPrecision) {
                GEN_WRITE_TABLE(FOR_ARRAY, /*Half=*/false, /*Align16=*/false)
            } else {
                GEN_WRITE_TABLE(FOR_ARRAY, /*Half=*/true,  /*Align16=*/false)
            }
        }
    }

#undef FOR_ARRAY
#undef FOR_NON_ARRAY
#undef GEN_WRITE_TABLE
}

#if defined(SK_DEBUG)

void UniformManager::checkBeginStruct(int baseAlignment) {
    // Wrote a struct field before the struct was started
    SkASSERT(fExpectedUniformIndex == 0);

    // Not expecting to start a struct (layout must be valid)
    SkASSERT(fSubstructCalculator.layout() != Layout::kInvalid);

    // Somehow already started a substruct (base alignment should be <= 0 initially)
    SkASSERT(fStructBaseAlignment <= 0);

    // Empty substructs are not allowed
    SkASSERT(!fExpectedUniforms.empty());

    // Assume the expected uniforms describe the whole substruct
    auto structCalculator = UniformOffsetCalculator::ForStruct(fLayout);
    for (const Uniform& f : fExpectedUniforms) {
        structCalculator.advanceOffset(f.type(), f.count());
    }

    // Calculated alignment must match the passed base alignment
    SkASSERT(baseAlignment == structCalculator.requiredAlignment());

    fSubstructStartingOffset = fOffsetCalculator.advanceStruct(structCalculator);
}

void UniformManager::checkEndStruct() {
    // Didn't write all the expected fields before ending the struct
    SkASSERT(fExpectedUniformIndex == (int)fExpectedUniforms.size());

    // Not expecting a struct (layout must be valid)
    SkASSERT(fSubstructCalculator.layout() != Layout::kInvalid);

    // Missing a beginStruct() (base alignment must be > 0 if we are in a struct)
    SkASSERT(fStructBaseAlignment > 0);

    // `fStructCalculator` should now have been advanced equivalently to the substruct calculator
    // used in checkBeginStruct() to calculate the expected starting offset.
    const int structSize = SkAlignTo(fSubstructCalculator.size(),
                                     fSubstructCalculator.requiredAlignment());

    // Somehow didn't end on the correct boundary
    SkASSERT(fStorage.size() == fSubstructStartingOffset + structSize);

    // UniformManager's alignment got out of sync with expected alignment
    SkASSERT(fReqAlignment == fOffsetCalculator.requiredAlignment());
    SkASSERT(fReqAlignment >= fSubstructCalculator.requiredAlignment());

    // Reset the substruct calculator to mark that the struct has been completed
    fSubstructCalculator = {};
}

void UniformManager::checkExpected(const void* dst, SkSLType type, int count) {
    // A write() outside of a UniformExpectationsVisitor or too many uniforms written for what
    // is expected.
    SkASSERT(fExpectedUniformIndex < SkTo<int>(fExpectedUniforms.size()));

    if (fSubstructCalculator.layout() != Layout::kInvalid) {
        // A write() that should be inside a struct, but missing a call to beginStruct()
        SkASSERT(fStructBaseAlignment > 0);
    } else {
        // A substruct was started when it shouldn't have been.
        SkASSERT(fStructBaseAlignment <= 0);
    }

    const Uniform& expected = fExpectedUniforms[fExpectedUniformIndex++];
    // Not all types are supported as uniforms or supported by UniformManager
    SkASSERT(SkSLTypeCanBeUniformValue(expected.type()));

    auto [expectedType, expectedCount] = adjust_for_matrix_type(expected.type(), expected.count());
    SkASSERT(expectedType == type && expectedCount == count);

    if (dst) {
        // If we have 'dst', it's the aligned starting offset of the uniform being checked, so
        // subtracting the address of the first byte in fStorage gives us the offset.
        int offset = static_cast<int>(reinterpret_cast<intptr_t>(dst) -
                                      reinterpret_cast<intptr_t>(fStorage.data()) -
                                      fNonShadingOffset);

        if (fSubstructCalculator.layout() == Layout::kInvalid) {
            // Pass original expected type and count to the offset calculator for validation.
            SkASSERT(offset == fOffsetCalculator.advanceOffset(expected.type(), expected.count()));
            SkASSERT(fReqAlignment == fOffsetCalculator.requiredAlignment());

            // And if it is the paint color uniform, we should not have already written it.
            SkASSERT(!(fWrotePaintColor && expected.isPaintColor()));
        } else {
            int relOffset = fSubstructCalculator.advanceOffset(expected.type(), expected.count());
            SkASSERT(offset == fSubstructStartingOffset + relOffset);

            // The overall required alignment might already be higher from prior fields, but should
            // be at least what's required by the substruct.
            SkASSERT(fReqAlignment >= fSubstructCalculator.requiredAlignment());

            // And it should not be a paint color uniform within a substruct.
            SkASSERT(!expected.isPaintColor());
        }
    } else {
        // If 'dst' is null, it's an already-visited paint color uniform, so it's not being written
        // and not changing the offset, and should not be part of a substruct.
        SkASSERT(fWrotePaintColor);
        SkASSERT(fSubstructCalculator.layout() == Layout::kInvalid);
        SkASSERT(expected.isPaintColor());
    }
}

bool UniformManager::isReset() const {
    return fStorage.empty();
}

void UniformManager::setExpectedUniforms(SkSpan<const Uniform> expected, bool isSubstruct) {
    fExpectedUniforms = expected;
    fExpectedUniformIndex = 0;

    if (isSubstruct) {
        // Start collecting the subsequent uniforms with a 0-based offset to determine their
        // relative layout and required base alignment of the entire struct.
        fSubstructCalculator = UniformOffsetCalculator::ForStruct(fLayout);
    } else {
        // Expected uniforms will advance fOffsetCalculator directly
        SkASSERT(fSubstructCalculator.layout() == Layout::kInvalid);
    }
}

void UniformManager::doneWithExpectedUniforms() {
    SkASSERT(fExpectedUniformIndex == static_cast<int>(fExpectedUniforms.size()));
    // Any expected substruct should have been ended and validated inside endStruct(); if this fails
    // it means there is a missing endStruct().
    SkASSERT(fSubstructCalculator.layout() == Layout::kInvalid);
    fExpectedUniforms = {};
}

#endif // SK_DEBUG

} // namespace skgpu::graphite
