/*
 * Copyright 2024 Alyssa Rosenzweig
 * Copyright 2022 Collabora, Ltd.
 * SPDX-License-Identifier: MIT
 *
 */

#include "layout.h"

#include "util/format/u_format.h"
#include <gtest/gtest.h>

/*
 * Reference tiling algorithm, written for clarity rather than performance. See
 * docs/drivers/asahi.rst for details on the format.
 */

static unsigned
z_order(unsigned x, unsigned y)
{
   unsigned out = 0;

   for (unsigned i = 0; i < 8; ++i) {
      unsigned bit = (1 << (2 * i));

      if (x & (1 << i))
         out |= bit;

      if (y & (1 << i))
         out |= bit << 1;
   }

   return out;
}

/* x/y are in blocks */
static unsigned
tiled_offset_el(struct ail_layout *layout, unsigned level, unsigned x_el,
                unsigned y_el)
{
   unsigned x_tl = x_el / layout->tilesize_el[level].width_el;
   unsigned y_tl = y_el / layout->tilesize_el[level].height_el;

   unsigned offs_x_el = x_el % layout->tilesize_el[level].width_el;
   unsigned offs_y_el = y_el % layout->tilesize_el[level].height_el;

   unsigned offs_in_tile_el = z_order(offs_x_el, offs_y_el);

   unsigned offs_row_el =
      y_tl *
      align(layout->stride_el[level], layout->tilesize_el[level].width_el) *
      layout->tilesize_el[level].height_el;

   unsigned offs_col_el = x_tl * layout->tilesize_el[level].width_el *
                          layout->tilesize_el[level].height_el;

   return offs_row_el + offs_col_el + offs_in_tile_el;
}

static unsigned
linear_offset_B(unsigned x_el, unsigned y_el, unsigned stride_B,
                unsigned blocksize_B)
{
   return (stride_B * y_el) + (x_el * blocksize_B);
}

static void
ref_access_tiled(uint8_t *tiled, uint8_t *linear, struct ail_layout *layout,
                 unsigned region_x_px, unsigned region_y_px, unsigned w_px,
                 unsigned h_px, uint32_t linear_stride_B, bool dst_is_tiled)
{
   unsigned blocksize_B = util_format_get_blocksize(layout->format);

   unsigned w_el = util_format_get_nblocksx(layout->format, w_px);
   unsigned h_el = util_format_get_nblocksy(layout->format, h_px);

   unsigned region_x_el = util_format_get_nblocksx(layout->format, region_x_px);
   unsigned region_y_el = util_format_get_nblocksy(layout->format, region_y_px);

   for (unsigned linear_y_el = 0; linear_y_el < h_el; ++linear_y_el) {
      for (unsigned linear_x_el = 0; linear_x_el < w_el; ++linear_x_el) {
         unsigned tiled_x_el = region_x_el + linear_x_el;
         unsigned tiled_y_el = region_y_el + linear_y_el;

         uint8_t *linear_ptr =
            linear + linear_offset_B(linear_x_el, linear_y_el, linear_stride_B,
                                     blocksize_B);

         unsigned twiddled_offs_B =
            blocksize_B * tiled_offset_el(layout, 0, tiled_x_el, tiled_y_el);

         /* While we're at it, check that ail_get_twiddled_block_B does the
          * right thing where it is defined (at tile boundaries). We can't
          * otherwise use it here as it is part of the routine under test.
          */
         if ((tiled_x_el % layout->tilesize_el[0].width_el) == 0 &&
             (tiled_y_el % layout->tilesize_el[0].height_el) == 0) {

            unsigned tiled_x_px =
               tiled_x_el * util_format_get_blockwidth(layout->format);

            unsigned tiled_y_px =
               tiled_y_el * util_format_get_blockwidth(layout->format);

            EXPECT_EQ(
               ail_get_twiddled_block_B(layout, 0, tiled_x_px, tiled_y_px, 0),
               twiddled_offs_B);
         }

         uint8_t *tiled_ptr = tiled + twiddled_offs_B;

         if (dst_is_tiled) {
            memcpy(tiled_ptr, linear_ptr, blocksize_B);
         } else {
            memcpy(linear_ptr, tiled_ptr, blocksize_B);
         }
      }
   }
}

/*
 * Helper to build test cases for tiled texture access. This test suite compares
 * the above reference tiling algorithm to the optimized algorithm used in
 * production.
 */
static void
test(unsigned width, unsigned height, unsigned rx, unsigned ry, unsigned rw,
     unsigned rh, unsigned linear_stride, enum pipe_format format, bool store)
{
   unsigned bpp = util_format_get_blocksize(format);
   struct ail_layout layout = {
      .width_px = width,
      .height_px = height,
      .depth_px = 1,
      .sample_count_sa = 1,
      .levels = 1,
      .tiling = AIL_TILING_GPU,
      .format = format,
   };

   ail_make_miptree(&layout);

   uint8_t *tiled = (uint8_t *)calloc(bpp, layout.size_B);
   uint8_t *linear = (uint8_t *)calloc(bpp, rh * linear_stride);
   uint8_t *ref =
      (uint8_t *)calloc(bpp, store ? layout.size_B : (rh * linear_stride));

   if (store) {
      for (unsigned i = 0; i < bpp * rh * linear_stride; ++i) {
         linear[i] = (i & 0xFF);
      }

      ail_tile(tiled, linear, &layout, 0, linear_stride, rx, ry, rw, rh);
      ref_access_tiled(ref, linear, &layout, rx, ry, rw, rh, linear_stride,
                       true);

      EXPECT_EQ(memcmp(ref, tiled, layout.size_B), 0);
   } else {
      for (unsigned i = 0; i < layout.size_B; ++i) {
         tiled[i] = (i & 0xFF);
      }

      ail_detile(tiled, linear, &layout, 0, linear_stride, rx, ry, rw, rh);
      ref_access_tiled(tiled, ref, &layout, rx, ry, rw, rh, linear_stride,
                       false);

      EXPECT_EQ(memcmp(ref, linear, bpp * rh * linear_stride), 0);
   }

   free(ref);
   free(tiled);
   free(linear);
}

static void
test_ldst(unsigned width, unsigned height, unsigned rx, unsigned ry,
          unsigned rw, unsigned rh, unsigned linear_stride,
          enum pipe_format format)
{
   test(width, height, rx, ry, rw, rh, linear_stride, format, true);
   test(width, height, rx, ry, rw, rh, linear_stride, format, false);
}

TEST(Twiddling, RegularFormats)
{
   test_ldst(233, 173, 0, 0, 233, 173, 233, PIPE_FORMAT_R8_UINT);
   test_ldst(233, 173, 0, 0, 233, 173, 233 * 2, PIPE_FORMAT_R8G8_UINT);
   test_ldst(233, 173, 0, 0, 233, 173, 233 * 4, PIPE_FORMAT_R32_UINT);
   test_ldst(173, 143, 0, 0, 173, 143, 173 * 8, PIPE_FORMAT_R32G32_UINT);
   test_ldst(133, 143, 0, 0, 133, 143, 133 * 16, PIPE_FORMAT_R32G32B32A32_UINT);
}

TEST(Twiddling, UnpackedStrides)
{
   test_ldst(213, 17, 0, 0, 213, 17, 369 * 1, PIPE_FORMAT_R8_SINT);
   test_ldst(213, 17, 0, 0, 213, 17, 369 * 2, PIPE_FORMAT_R8G8_SINT);
   test_ldst(213, 17, 0, 0, 213, 17, 369 * 4, PIPE_FORMAT_R32_SINT);
   test_ldst(213, 17, 0, 0, 213, 17, 369 * 8, PIPE_FORMAT_R32G32_SINT);
   test_ldst(213, 17, 0, 0, 213, 17, 369 * 16, PIPE_FORMAT_R32G32B32A32_SINT);
}

TEST(Twiddling, PartialAccess)
{
   test_ldst(283, 171, 3, 1, 131, 7, 369 * 1, PIPE_FORMAT_R8_UNORM);
   test_ldst(283, 171, 3, 1, 131, 7, 369 * 2, PIPE_FORMAT_R8G8_UNORM);
   test_ldst(283, 171, 3, 1, 131, 7, 369 * 4, PIPE_FORMAT_R32_UNORM);
   test_ldst(283, 171, 3, 1, 131, 7, 369 * 8, PIPE_FORMAT_R32G32_UNORM);
   test_ldst(283, 171, 3, 1, 131, 7, 369 * 16, PIPE_FORMAT_R32G32B32A32_UNORM);
}

TEST(Twiddling, ETC)
{
   /* Block alignment assumed */
   test_ldst(32, 32, 0, 0, 32, 32, 512, PIPE_FORMAT_ETC1_RGB8);
   test_ldst(32, 256, 0, 0, 32, 256, 512, PIPE_FORMAT_ETC2_RGB8A1);
   test_ldst(32, 512, 0, 0, 32, 512, 512, PIPE_FORMAT_ETC2_RG11_SNORM);
}

TEST(Twiddling, PartialETC)
{
   /* Block alignment assumed */
   test_ldst(32, 32, 4, 8, 16, 12, 512, PIPE_FORMAT_ETC1_RGB8);
   test_ldst(32, 32, 4, 8, 16, 12, 512, PIPE_FORMAT_ETC2_RGB8A1);
   test_ldst(32, 32, 4, 8, 16, 12, 512, PIPE_FORMAT_ETC2_RG11_SNORM);
}

TEST(Twiddling, DXT)
{
   /* Block alignment assumed */
   test_ldst(32, 32, 0, 0, 32, 32, 512, PIPE_FORMAT_DXT1_RGB);
   test_ldst(32, 32, 0, 0, 32, 32, 512, PIPE_FORMAT_DXT3_RGBA);
   test_ldst(32, 32, 0, 0, 32, 32, 512, PIPE_FORMAT_DXT5_RGBA);
}

TEST(Twiddling, PartialDXT)
{
   /* Block alignment assumed */
   test_ldst(32, 32, 4, 8, 16, 12, 512, PIPE_FORMAT_DXT1_RGB);
   test_ldst(32, 32, 4, 8, 16, 12, 512, PIPE_FORMAT_DXT3_RGBA);
   test_ldst(32, 32, 4, 8, 16, 12, 512, PIPE_FORMAT_DXT5_RGBA);
}

TEST(Twiddling, ASTC)
{
   /* Block alignment assumed */
   test_ldst(40, 40, 0, 0, 40, 40, 512, PIPE_FORMAT_ASTC_4x4);
   test_ldst(50, 40, 0, 0, 50, 40, 512, PIPE_FORMAT_ASTC_5x4);
   test_ldst(50, 50, 0, 0, 50, 50, 512, PIPE_FORMAT_ASTC_5x5);
}

TEST(Twiddling, PartialASTC)
{
   /* Block alignment assumed */
   test_ldst(40, 40, 4, 4, 16, 8, 512, PIPE_FORMAT_ASTC_4x4);
   test_ldst(50, 40, 5, 4, 10, 8, 512, PIPE_FORMAT_ASTC_5x4);
   test_ldst(50, 50, 5, 5, 10, 10, 512, PIPE_FORMAT_ASTC_5x5);
}
