/*
 * Copyright 2019 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "isl/isl.h"

#ifdef IN_UNIT_TEST
/* STATIC_ASSERT is a do { ... } while(0) statement */
UNUSED static void static_assert_func(void) {
   STATIC_ASSERT(ISL_AUX_OP_ASSERT == ((enum isl_aux_op) 0));
   STATIC_ASSERT(ISL_AUX_STATE_ASSERT == ((enum isl_aux_state) 0));
}

#undef unreachable
#define unreachable(str) return 0

#undef assert
#define assert(cond) do { \
   if (!(cond)) { \
      return 0; \
   } \
} while (0)
#endif

/* How writes with an isl_aux_usage behave. */
enum write_behavior {
   /* Writes only touch the main surface. */
   WRITES_ONLY_TOUCH_MAIN = 0,

   /* Writes using the 3D engine are compressed. */
   WRITES_COMPRESS,

   /* Writes implicitly fully resolve the compression block and write the data
    * uncompressed into the main surface. The resolved aux blocks are
    * ambiguated and left in the pass-through state.
    */
   WRITES_RESOLVE_AMBIGUATE,
};

/* A set of features supported by an isl_aux_usage. */
struct aux_usage_info {

   /* How writes affect the surface(s) in use. */
   enum write_behavior write_behavior;

   /* Aux supports "real" compression beyond just fast-clears. */
   bool compressed;

   /* SW can perform ISL_AUX_OP_FAST_CLEAR. */
   bool fast_clear;

   /* SW can perform ISL_AUX_OP_PARTIAL_RESOLVE. */
   bool partial_resolve;

   /* Performing ISL_AUX_OP_FULL_RESOLVE includes ISL_AUX_OP_AMBIGUATE. */
   bool full_resolves_ambiguate;
};

#define AUX(wb, c, fc, pr, fra, type)                   \
   [ISL_AUX_USAGE_ ## type] = { WRITES_ ## wb, c, fc, pr, fra},
#define Y true
#define x false
static const struct aux_usage_info info[] = {
/*         write_behavior c fc pr fra */
   AUX(         COMPRESS, Y, Y, x, x, HIZ)
   AUX(         COMPRESS, Y, Y, x, x, HIZ_CCS)
   AUX(         COMPRESS, Y, Y, x, x, HIZ_CCS_WT)
   AUX(         COMPRESS, Y, Y, Y, x, MCS)
   AUX(         COMPRESS, Y, Y, Y, x, MCS_CCS)
   AUX(         COMPRESS, Y, Y, Y, Y, CCS_E)
   AUX(RESOLVE_AMBIGUATE, x, Y, x, Y, CCS_D)
   AUX(RESOLVE_AMBIGUATE, Y, x, x, Y, MC)
   AUX(         COMPRESS, Y, x, x, Y, STC_CCS)
};
#undef x
#undef Y
#undef AUX

ASSERTED static bool
aux_state_possible(enum isl_aux_state state,
                   enum isl_aux_usage usage)
{
   switch (state) {
   case ISL_AUX_STATE_CLEAR:
   case ISL_AUX_STATE_PARTIAL_CLEAR:
      return info[usage].fast_clear;
   case ISL_AUX_STATE_COMPRESSED_CLEAR:
      return info[usage].fast_clear && info[usage].compressed;
   case ISL_AUX_STATE_COMPRESSED_NO_CLEAR:
      return info[usage].compressed;
   case ISL_AUX_STATE_RESOLVED:
   case ISL_AUX_STATE_PASS_THROUGH:
   case ISL_AUX_STATE_AUX_INVALID:
      return true;
#ifdef IN_UNIT_TEST
   case ISL_AUX_STATE_ASSERT:
      break;
#endif
   }

   unreachable("Invalid aux state.");
}

enum isl_aux_op
isl_aux_prepare_access(enum isl_aux_state initial_state,
                       enum isl_aux_usage usage,
                       bool fast_clear_supported)
{
   if (usage != ISL_AUX_USAGE_NONE) {
      UNUSED const enum isl_aux_usage state_superset_usage =
         usage == ISL_AUX_USAGE_CCS_D ? ISL_AUX_USAGE_CCS_E : usage;
      assert(aux_state_possible(initial_state, state_superset_usage));
   }
   assert(!fast_clear_supported || info[usage].fast_clear);

   switch (initial_state) {
   case ISL_AUX_STATE_COMPRESSED_CLEAR:
      if (!info[usage].compressed)
         return ISL_AUX_OP_FULL_RESOLVE;
      /* Fall-through */
   case ISL_AUX_STATE_CLEAR:
   case ISL_AUX_STATE_PARTIAL_CLEAR:
      return fast_clear_supported ?
                ISL_AUX_OP_NONE :
             info[usage].partial_resolve ?
                ISL_AUX_OP_PARTIAL_RESOLVE : ISL_AUX_OP_FULL_RESOLVE;
   case ISL_AUX_STATE_COMPRESSED_NO_CLEAR:
      return info[usage].compressed ?
             ISL_AUX_OP_NONE : ISL_AUX_OP_FULL_RESOLVE;
   case ISL_AUX_STATE_RESOLVED:
   case ISL_AUX_STATE_PASS_THROUGH:
      return ISL_AUX_OP_NONE;
   case ISL_AUX_STATE_AUX_INVALID:
      return info[usage].write_behavior == WRITES_ONLY_TOUCH_MAIN ?
             ISL_AUX_OP_NONE : ISL_AUX_OP_AMBIGUATE;
#ifdef IN_UNIT_TEST
   case ISL_AUX_STATE_ASSERT:
      break;
#endif
   }

   unreachable("Invalid aux state.");
}

enum isl_aux_state
isl_aux_state_transition_aux_op(enum isl_aux_state initial_state,
                                enum isl_aux_usage usage,
                                enum isl_aux_op op)
{
   assert(aux_state_possible(initial_state, usage));
   assert(usage != ISL_AUX_USAGE_NONE || op == ISL_AUX_OP_NONE);

   switch (op) {
   case ISL_AUX_OP_NONE:
      return initial_state;
   case ISL_AUX_OP_FAST_CLEAR:
      assert(info[usage].fast_clear);
      return ISL_AUX_STATE_CLEAR;
   case ISL_AUX_OP_PARTIAL_RESOLVE:
      assert(isl_aux_state_has_valid_aux(initial_state));
      assert(info[usage].partial_resolve);
      return initial_state == ISL_AUX_STATE_CLEAR ||
             initial_state == ISL_AUX_STATE_PARTIAL_CLEAR ||
             initial_state == ISL_AUX_STATE_COMPRESSED_CLEAR ?
             ISL_AUX_STATE_COMPRESSED_NO_CLEAR : initial_state;
   case ISL_AUX_OP_FULL_RESOLVE:
      assert(isl_aux_state_has_valid_aux(initial_state));
      return info[usage].full_resolves_ambiguate ||
             initial_state == ISL_AUX_STATE_PASS_THROUGH ?
             ISL_AUX_STATE_PASS_THROUGH : ISL_AUX_STATE_RESOLVED;
   case ISL_AUX_OP_AMBIGUATE:
      return ISL_AUX_STATE_PASS_THROUGH;
#if IN_UNIT_TEST
   case ISL_AUX_OP_ASSERT:
      break;
#endif
   }

   unreachable("Invalid aux op.");
}

enum isl_aux_state
isl_aux_state_transition_write(enum isl_aux_state initial_state,
                               enum isl_aux_usage usage,
                               bool full_surface)
{
   if (info[usage].write_behavior == WRITES_ONLY_TOUCH_MAIN) {
      assert(full_surface || isl_aux_state_has_valid_primary(initial_state));

      return initial_state == ISL_AUX_STATE_PASS_THROUGH ?
             ISL_AUX_STATE_PASS_THROUGH : ISL_AUX_STATE_AUX_INVALID;
   }

   assert(isl_aux_state_has_valid_aux(initial_state));
   assert(aux_state_possible(initial_state, usage));
   assert(info[usage].write_behavior == WRITES_COMPRESS ||
          info[usage].write_behavior == WRITES_RESOLVE_AMBIGUATE);

   if (full_surface) {
      return info[usage].write_behavior == WRITES_COMPRESS ?
             ISL_AUX_STATE_COMPRESSED_NO_CLEAR : ISL_AUX_STATE_PASS_THROUGH;
   }

   switch (initial_state) {
   case ISL_AUX_STATE_CLEAR:
   case ISL_AUX_STATE_PARTIAL_CLEAR:
      return info[usage].write_behavior == WRITES_COMPRESS ?
             ISL_AUX_STATE_COMPRESSED_CLEAR : ISL_AUX_STATE_PARTIAL_CLEAR;
   case ISL_AUX_STATE_RESOLVED:
   case ISL_AUX_STATE_PASS_THROUGH:
      return info[usage].write_behavior == WRITES_COMPRESS ?
             ISL_AUX_STATE_COMPRESSED_NO_CLEAR : initial_state;
   case ISL_AUX_STATE_COMPRESSED_CLEAR:
   case ISL_AUX_STATE_COMPRESSED_NO_CLEAR:
   case ISL_AUX_STATE_AUX_INVALID:
      return initial_state;
#ifdef IN_UNIT_TEST
   case ISL_AUX_STATE_ASSERT:
      break;
#endif
   }

   unreachable("Invalid aux state.");
}

bool
isl_aux_usage_has_fast_clears(enum isl_aux_usage usage)
{
   return info[usage].fast_clear;
}
