// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/client/transfer_buffer_cmd_copy_helpers.h"

#include <type_traits>

#include "base/containers/span.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace gpu {

namespace {

// Define a fake scoped transfer buffer to test helpers
class FakeScopedTransferBufferPtr {
 public:
  FakeScopedTransferBufferPtr(uint32_t max_size)
      : valid_(false), max_size_(max_size), buffer_() {}

  void Reset(uint32_t size) {
    buffer_.resize(std::min(max_size_, size));
    std::fill(buffer_.begin(), buffer_.end(), 0);
    valid_ = true;
  }
  void Release() { buffer_.clear(); }
  uint32_t size() const { return static_cast<uint32_t>(buffer_.size()); }
  bool valid() const { return valid_; }
  void* address() { return buffer_.data(); }
  base::span<uint8_t> data() { return buffer_; }

 private:
  bool valid_;
  uint32_t max_size_;
  std::vector<uint8_t> buffer_;
};

constexpr uint32_t MaxCopyCount(uint32_t buffer_size) {
  return ComputeMaxCopyCount<char, short, float, size_t>(buffer_size);
}

template <typename T>
void ExpectEqualCopy(base::span<const uint8_t> transfer_buffer_data,
                     uint32_t transfer_buffer_offset,
                     const std::vector<T>& expected,
                     uint32_t element_offset,
                     uint32_t elements_to_compare) {
  base::span<const uint8_t> expected_bytes = {};
  // A bytewise comparison of floats is done, necessitating the use of
  // `base::allow_nonunique_obj_t`.
  if constexpr (!std::has_unique_object_representations_v<T>) {
    expected_bytes = base::as_byte_span(base::allow_nonunique_obj, expected);
  } else {
    expected_bytes = base::as_byte_span(expected);
  }

  size_t byte_offset = element_offset * sizeof(T);
  size_t bytes_to_compare = elements_to_compare * sizeof(T);
  EXPECT_EQ(
      transfer_buffer_data.subspan(transfer_buffer_offset, bytes_to_compare),
      expected_bytes.subspan(byte_offset, bytes_to_compare));
}

}  // namespace

class TransferBufferCmdCopyHelpersTest : public testing::Test {
 protected:
  struct BigStruct {
    std::array<char, UINT32_MAX> a;
  };
  struct ExpectedBuffers {
    std::vector<char> a;
    std::vector<short> b;
    std::vector<float> c;
    std::vector<size_t> d;

    ExpectedBuffers(uint32_t count) : a(count), b(count), c(count), d(count) {
      uint32_t j = 0;
      for (uint32_t i = 0; i < count; ++i) {
        a[i] = static_cast<char>(j++);
      }
      for (uint32_t i = 0; i < count; ++i) {
        b[i] = static_cast<short>(j++);
      }
      for (uint32_t i = 0; i < count; ++i) {
        c[i] = static_cast<float>(j++);
      }
      for (uint32_t i = 0; i < count; ++i) {
        d[i] = static_cast<size_t>(j++);
      }
    }
  };

  template <uint32_t BufferSize>
  void CheckTransferArraysAndExecute(uint32_t count) {
    FakeScopedTransferBufferPtr transfer_buffer(BufferSize);
    ExpectedBuffers expected(count);

    EXPECT_TRUE(::internal::TransferArraysAndExecute(
        count, &transfer_buffer,
        [&](std::array<uint32_t, 4>& byte_offsets, uint32_t copy_offset,
            uint32_t copy_count) {
          // Check that each sub-copy is correct
          ExpectEqualCopy(transfer_buffer.data(), byte_offsets[0], expected.a,
                          copy_offset, copy_count);
          ExpectEqualCopy(transfer_buffer.data(), byte_offsets[1], expected.b,
                          copy_offset, copy_count);
          ExpectEqualCopy(transfer_buffer.data(), byte_offsets[2], expected.c,
                          copy_offset, copy_count);
          ExpectEqualCopy(transfer_buffer.data(), byte_offsets[3], expected.d,
                          copy_offset, copy_count);
        },
        expected.a.data(), expected.b.data(), expected.c.data(),
        expected.d.data()));
  }
};

// Check packed size computation
TEST_F(TransferBufferCmdCopyHelpersTest, CheckedSizeOfTypes) {
  EXPECT_EQ((SizeOfPackedTypes<uint32_t, char>()), sizeof(bool));
  EXPECT_EQ((SizeOfPackedTypes<uint32_t, int>()), sizeof(int));
  EXPECT_EQ((SizeOfPackedTypes<uint32_t, float>()), sizeof(float));
  EXPECT_EQ((SizeOfPackedTypes<uint32_t, float, int>()),
            sizeof(float) + sizeof(int));
  EXPECT_EQ((SizeOfPackedTypes<uint32_t, BigStruct>()), sizeof(BigStruct));
}

// Check copy size computations which do not require padding elements
TEST_F(TransferBufferCmdCopyHelpersTest, ComputeCombinedCopySizeAligned) {
  EXPECT_EQ((ComputeCombinedCopySize<char, int, float>(4)),
            4 * sizeof(char) + 4 * sizeof(int) + 4 * sizeof(float));

  EXPECT_EQ((ComputeCombinedCopySize<float, int, char>(3)),
            3 * sizeof(float) + 3 * sizeof(int) + 3 * sizeof(char));

  EXPECT_EQ((ComputeCombinedCopySize<BigStruct>(1)), sizeof(BigStruct));
}

// Check copy size computations where elements do require padding
TEST_F(TransferBufferCmdCopyHelpersTest, ComputeCombinedCopySizeUnaligned) {
  EXPECT_EQ((ComputeCombinedCopySize<char, int, float>(3)),
            4 * sizeof(char) + 3 * sizeof(int) + 3 * sizeof(float));

  EXPECT_EQ((ComputeCombinedCopySize<char, int, float>(5)),
            8 * sizeof(char) + 5 * sizeof(int) + 5 * sizeof(float));
}

// Check that overflow in copy size computation returns UINT32_MAX
TEST_F(TransferBufferCmdCopyHelpersTest, ComputeCombinedCopySizeOverflow) {
  EXPECT_EQ((ComputeCombinedCopySize<BigStruct, short>(1)), UINT32_MAX);
  EXPECT_EQ((ComputeCombinedCopySize<short, BigStruct>(1)), UINT32_MAX);
  EXPECT_EQ((ComputeCombinedCopySize<float>(UINT32_MAX / sizeof(float) + 1)),
            UINT32_MAX);
  EXPECT_EQ((ComputeCombinedCopySize<BigStruct, BigStruct>(2)), UINT32_MAX);
}

// Check that the computed copy count is correct when padding is not required
TEST_F(TransferBufferCmdCopyHelpersTest, ComputeMaxCopyCountAligned) {
  EXPECT_EQ((ComputeMaxCopyCount<BigStruct>(UINT32_MAX)), 1u);
  EXPECT_EQ((ComputeMaxCopyCount<int, float>(64)), 8u);
  EXPECT_EQ((ComputeMaxCopyCount<char>(64)), 64u);
  EXPECT_EQ((ComputeMaxCopyCount<short, char, char>(64)), 16u);
}

// Check that the computed copy count is correct when padding is required
TEST_F(TransferBufferCmdCopyHelpersTest, ComputeMaxCopyCountUnaligned) {
  EXPECT_EQ((ComputeMaxCopyCount<char, int, float>(64)), 7u);
  EXPECT_EQ((ComputeMaxCopyCount<char, short, int>(64)), 9u);
}

// Check that computing copy count for a buffer of size 0 is 0;
TEST_F(TransferBufferCmdCopyHelpersTest, ComputeMaxCopyCountZero) {
  uint32_t buffer_size = 0;
  EXPECT_EQ((ComputeMaxCopyCount<char>(buffer_size)), 0u);
  EXPECT_EQ((ComputeMaxCopyCount<int, float>(buffer_size)), 0u);
  EXPECT_EQ((ComputeMaxCopyCount<BigStruct>(buffer_size)), 0u);
}

// Check that copy count for elements whose packed size fits in the buffer
// but computed aligned size does not is 0
TEST_F(TransferBufferCmdCopyHelpersTest, ComputeMaxCopyCountOverflow) {
  EXPECT_EQ((ComputeMaxCopyCount<char, float>(
                SizeOfPackedTypes<uint32_t, char, float>())),
            0u);
  EXPECT_EQ((ComputeMaxCopyCount<short, float>(
                SizeOfPackedTypes<uint32_t, short, float>())),
            0u);
  EXPECT_EQ((ComputeMaxCopyCount<char, size_t>(
                SizeOfPackedTypes<uint32_t, char, size_t>())),
            0u);
  EXPECT_EQ((ComputeMaxCopyCount<short, size_t>(
                SizeOfPackedTypes<uint32_t, short, size_t>())),
            0u);
}

// Check that copied results are as expected and correctly aligned
TEST_F(TransferBufferCmdCopyHelpersTest, TransferArraysAndExecute) {
  // Aligned: Copy 1 element from each buffer into a transfer buffer of 256
  // bytes
  CheckTransferArraysAndExecute<256>(1);

  // Aligned: Copy as many elements as possible from each buffer into a transfer
  // buffer of 256 bytes
  CheckTransferArraysAndExecute<256>(MaxCopyCount(256));

  // Unaligned: Copy 1 element from each buffer into a transfer buffer of 256
  // bytes
  CheckTransferArraysAndExecute<257>(1);

  // Unaligned: Copy as many elements as possible from each buffer into a
  // transfer buffer of 257 bytes
  CheckTransferArraysAndExecute<257>(MaxCopyCount(257));

  // Large: Copy 1 element from each buffer into a transfer buffer of UINT32_MAX
  // bytes
  CheckTransferArraysAndExecute<UINT32_MAX>(1);

  // Large: Copy as many elements as possible from each buffer into a transfer
  // buffer of 256 bytes
  CheckTransferArraysAndExecute<UINT32_MAX>(MaxCopyCount(256));
}

// Check copies that overflow and require multiple transfer buffers
TEST_F(TransferBufferCmdCopyHelpersTest, TransferArraysAndExecuteOverflow) {
  // Check aligned transfers
  CheckTransferArraysAndExecute<256>(256);
  CheckTransferArraysAndExecute<256>(512);
  CheckTransferArraysAndExecute<4096>(64 * MaxCopyCount(4096));

  // Check unaligned transfers
  CheckTransferArraysAndExecute<257>(256);
  CheckTransferArraysAndExecute<253>(513);
  CheckTransferArraysAndExecute<4097>(MaxCopyCount(4097));
}

}  // namespace gpu
