/*
 * Copyright (C) 2026 Collabora, Ltd.
 * SPDX-License-Identifier: MIT
 */
#include "pan_fb.h"

#include "pan_afbc.h"
#include "pan_afrc.h"
#include "pan_desc.h"
#include "pan_encoder.h"
#include "pan_format.h"
#include "pan_image.h"
#include "pan_props.h"
#include "pan_util.h"

static unsigned
pan_bytes_per_pixel_tib(enum pipe_format format)
{
   const struct pan_blendable_format *bf =
      GENX(pan_blendable_format_from_pipe_format)(format);
   return pan_format_tib_size(format, bf->internal);
}

void
GENX(pan_select_fb_tile_size)(struct pan_fb_layout *fb)
{
   uint32_t rt_B_per_sa = 0;
   for (unsigned rt = 0; rt < fb->rt_count; rt++) {
      enum pipe_format format = fb->rt_formats[rt];
      if (format == PIPE_FORMAT_NONE)
         continue;

      const struct pan_blendable_format *bf =
         GENX(pan_blendable_format_from_pipe_format)(format);
      rt_B_per_sa += pan_format_tib_size(format, bf->internal);
   }

   /* The PLS area overlaps with the color targets */
   rt_B_per_sa = MAX2(rt_B_per_sa, fb->pls_size_B);

   const uint32_t rt_B_per_px = rt_B_per_sa * fb->sample_count;

   /* We always have depth and it's is always stored in a 32-bit float.
    * Stencil requires depth to be allocated, but doesn't have it's own
    * budget; it's tied to the depth buffer.
    */
   const uint32_t z_B_per_px = sizeof(float) * fb->sample_count;

   fb->tile_size_px =
      MIN2(fb->tile_rt_budget_B >> util_logbase2_ceil(rt_B_per_px),
           fb->tile_z_budget_B >> util_logbase2_ceil(z_B_per_px));

   /* Check if we're using too much tile-memory; if we are, try disabling
    * pipelining. This works because we're starting with an optimistic half
    * of the tile-budget, so we actually have another half that can be used.
    *
    * On v6 GPUs, doing this is not allowed; they *have* to pipeline.
    */
    if (PAN_ARCH != 6 && fb->tile_size_px < 4 * 4)
       fb->tile_size_px *= 2;

   /* Clamp tile size to hardware limits */
   fb->tile_size_px =
      MIN2(fb->tile_size_px, pan_max_effective_tile_size(PAN_ARCH));
   assert(util_is_power_of_two_nonzero(fb->tile_size_px));
   assert(fb->tile_size_px >= 4 * 4);

   /* Colour buffer allocations must be 1K aligned. */
   fb->tile_rt_alloc_B = ALIGN_POT(rt_B_per_px * fb->tile_size_px, 1024);

#if PAN_ARCH == 6
   assert(fb->tile_rt_alloc_B <= fb->tile_rt_budget_B && "tile too big");
#else
   assert(fb->tile_rt_alloc_B <= fb->tile_rt_budget_B * 2 && "tile too big");
#endif
}

/**
 * Returns true if there's enough space in the tile buffer for at least two
 * Z/S tiles.
 */
static inline bool
pan_fb_can_pipeline_zs(const struct pan_fb_layout *fb)
{
   const uint32_t z_B_per_px = sizeof(float) * fb->sample_count;
   const uint32_t z_B_per_tile = z_B_per_px * fb->tile_size_px;

   /* The budget is already half the available Z space */
   return z_B_per_tile < fb->tile_z_budget_B;
}

static void
align_fb_tiling_area_for_image_plane(struct pan_fb_layout *fb,
                                     struct pan_image_plane_ref pref)
{
   if (!pref.image)
      return;

   struct pan_image_block_size block_size;
   if (drm_is_afbc(pref.image->props.modifier)) {
      /* For AFBC render targets, the hardware always writes full superblocks.
       * In order to ensure we don't write garbage, we need to expand the
       * render area accordingly and load the border pixels.
       */
      block_size = pan_afbc_renderblock_size(pref.image->props.modifier);
      assert(block_size.width >= 16 && block_size.height >= 16);
   } else if (drm_is_afrc(pref.image->props.modifier)) {
      /* For AFRC render targets, the hardware always writes full clumps.  In
       * order to ensure we don't write garbage, we need to expand the render
       * area accordingly and load the border pixels.
       */
      bool scan = pan_afrc_is_scan(pref.image->props.modifier);
      block_size = pan_afrc_clump_size(pref.image->props.format, scan);
   } else {
      /* No alignment requirements */
      return;
   }

   fb->tiling_area_px = pan_fb_bbox_align(fb->tiling_area_px,
                                          block_size.width,
                                          block_size.height);
}

void
GENX(pan_align_fb_tiling_area)(struct pan_fb_layout *fb,
                               const struct pan_fb_store *store)
{
   if (store == NULL)
      return;

   for (unsigned rt = 0; rt < fb->rt_count; rt++) {
      if (store->rts[rt].store) {
         align_fb_tiling_area_for_image_plane(fb,
            pan_image_view_get_color_plane(store->rts[rt].iview));
      }
   }

   if (store->zs.store) {
      align_fb_tiling_area_for_image_plane(fb,
         pan_image_view_get_zs_plane(store->zs.iview));
   }

   if (store->s.store) {
      align_fb_tiling_area_for_image_plane(fb,
         pan_image_view_get_s_plane(store->s.iview));
   }
}

static void
fold_sample_0_image_load(struct pan_fb_resolve_op_msaa *rm,
                         const struct pan_image_view *iview,
                         uint8_t fb_sample_count)
{
   if (rm->resolve != PAN_FB_RESOLVE_IMAGE)
      return;

   if (rm->msaa != PAN_FB_MSAA_COPY_ALL)
      return;

   assert(pan_image_view_get_nr_samples(iview) == fb_sample_count);

   /* If we're loading all samples of the image but only storing SAMPLE_0,
    * then we don't actually need all samples in the image.  Drop it to a
    * SAMPLE_0 load to save bandwidth.
    */
   rm->msaa = PAN_FB_MSAA_COPY_SAMPLE_0;
}

static bool
resolve_msaa_can_fold(struct pan_fb_resolve_op_msaa rm,
                      enum pan_fb_resolve_op self_op,
                      enum pipe_format format)
{
   if (rm.resolve == PAN_FB_RESOLVE_IMAGE) {
      /* We already folded away COPY_ALL and all the other MSAA modes produce
       * a single per-pixel result across all the samples, i.e. COPY_IDENTICAL.
       * We can fold that with any resolve mode.
       */
      assert(rm.msaa != PAN_FB_MSAA_COPY_ALL);
      return true;
   }

   if (rm.resolve != self_op)
      return false;

   /* PAN_FB_MSAA_COPY_IDENTICAL is always foldable */
   if (rm.msaa == PAN_FB_MSAA_COPY_IDENTICAL)
      return true;

   if (self_op == PAN_FB_RESOLVE_Z || self_op == PAN_FB_RESOLVE_S) {
      /* Z/S only supports SAMPLE_0 */
      return rm.msaa == PAN_FB_MSAA_COPY_SAMPLE_0;
   } else {
      if (rm.msaa == PAN_FB_MSAA_COPY_AVERAGE &&
          !GENX(pan_format_supports_msaa_average)(format))
         return false;

      /* The color hardware supports SAMPLE_0 and AVERAGE */
      return rm.msaa == PAN_FB_MSAA_COPY_SAMPLE_0 ||
             rm.msaa == PAN_FB_MSAA_COPY_AVERAGE;
   }
}

static void
fold_resolve_into_store_target(enum pipe_format format,
                               uint8_t fb_sample_count,
                               enum pan_fb_resolve_op self_op,
                               struct pan_fb_resolve_target *resolve,
                               struct pan_fb_store_target *store)
{
   if (format == PIPE_FORMAT_NONE)
      return;

   /* We need SAMPLE_0 if we're going to fold in the resolve */
   if (store->msaa != PAN_FB_MSAA_COPY_SAMPLE_0)
      return;

   fold_sample_0_image_load(&resolve->in_bounds, resolve->iview,
                            fb_sample_count);
   fold_sample_0_image_load(&resolve->in_bounds, resolve->iview,
                            fb_sample_count);

   if (!resolve_msaa_can_fold(resolve->in_bounds, self_op, format) ||
       !resolve_msaa_can_fold(resolve->border, self_op, format))
      return;

   enum pan_fb_msaa_copy_op msaa;
   if (resolve->in_bounds.resolve == PAN_FB_RESOLVE_IMAGE) {
      /* If they're both IMAGE, there's nothing to fold */
      if (resolve->border.resolve == PAN_FB_RESOLVE_IMAGE)
         return;
      msaa = resolve->border.msaa;
   } else if (resolve->border.resolve == PAN_FB_RESOLVE_IMAGE) {
      msaa = resolve->in_bounds.msaa;
   } else {
      /* If neither is an image resolve, the MSAA copy op has to match or one
       * of them has to be PAN_FB_MSAA_COPY_IDENTICAL.
       */
      if (resolve->in_bounds.msaa != PAN_FB_MSAA_COPY_IDENTICAL) {
         if (resolve->border.msaa != PAN_FB_MSAA_COPY_IDENTICAL &&
             resolve->border.msaa != resolve->in_bounds.msaa)
            return;
         msaa = resolve->in_bounds.msaa;
      } else {
         msaa = resolve->border.msaa;
      }
   }

   /* PAN_FB_MSAA_COPY_IDENTICAL isn't real. Use SAMPLE_0 */
   if (msaa == PAN_FB_MSAA_COPY_IDENTICAL)
      msaa = PAN_FB_MSAA_COPY_SAMPLE_0;

   /* At this point, we know we can fold the resolve */
   store->msaa = msaa;

   if (resolve->in_bounds.resolve != PAN_FB_RESOLVE_IMAGE) {
      resolve->in_bounds.resolve = PAN_FB_RESOLVE_NONE;
      resolve->in_bounds.msaa = PAN_FB_MSAA_COPY_ALL;
   }

   if (resolve->border.resolve != PAN_FB_RESOLVE_IMAGE) {
      resolve->border.resolve = PAN_FB_RESOLVE_NONE;
      resolve->border.msaa = PAN_FB_MSAA_COPY_ALL;
   }
}

void
GENX(pan_fb_fold_resolve_into_store)(const struct pan_fb_layout *fb,
                                     struct pan_fb_resolve *resolve,
                                     struct pan_fb_store *store)
{
   if (fb->sample_count == 1)
      return;

   fold_resolve_into_store_target(fb->z_format, fb->sample_count,
                                  PAN_FB_RESOLVE_Z, &resolve->z, &store->zs);
   fold_resolve_into_store_target(fb->s_format, fb->sample_count,
                                  PAN_FB_RESOLVE_S, &resolve->s, &store->s);
   for (unsigned rt = 0; rt < fb->rt_count; rt++) {
      fold_resolve_into_store_target(fb->rt_formats[rt], fb->sample_count,
                                     PAN_FB_RESOLVE_RT(rt),
                                     &resolve->rts[rt], &store->rts[rt]);
   }
}

void
GENX(pan_fill_fb_info)(const struct pan_fb_desc_info *info,
                       struct pan_fb_info *fbinfo)
{
   struct pan_bbox bbox = { 0, 0, 0, 0 };
   if (info->fb->width_px > 0 && info->fb->width_px > 0) {
      const struct pan_fb_bbox fb_area_px =
         pan_fb_bbox_from_xywh(0, 0, info->fb->width_px, info->fb->height_px);

      assert(pan_fb_bbox_is_valid(info->fb->tiling_area_px));
      const struct pan_fb_bbox bbox_px =
         pan_fb_bbox_clamp(info->fb->tiling_area_px, fb_area_px);

      bbox = (struct pan_bbox) {
         .minx = bbox_px.min_x,
         .miny = bbox_px.min_y,
         .maxx = bbox_px.max_x,
         .maxy = bbox_px.max_y,
      };
   }

   *fbinfo = (struct pan_fb_info) {
      .width = info->fb->width_px,
      .height = info->fb->height_px,
      .draw_extent = bbox,
      .frame_bounding_box = bbox,
      .nr_samples = info->fb->sample_count,
      .rt_count = info->fb->rt_count,
      .pls_enabled = info->fb->pls_size_B > 0,
      .bifrost.pre_post = {
         .dcds.gpu = info->frame_shaders.dcd_pointer,
         .modes = {
            info->frame_shaders.modes[0],
            info->frame_shaders.modes[1],
            info->frame_shaders.modes[2],
         },
      },

      .tile_buf_budget = info->fb->tile_rt_budget_B,
      .z_tile_buf_budget = info->fb->tile_z_budget_B,
      .tile_size = info->fb->tile_size_px,
      .cbuf_allocation = info->fb->tile_rt_alloc_B,

      .sample_positions = info->sample_pos_array_pointer,
      .sprite_coord_origin = info->sprite_coord_origin_max_y,
      .first_provoking_vertex = info->provoking_vertex_first,
      .allow_hsr_prepass = info->provoking_vertex_first,
   };

   /* There are cases where we only want to fill out a partial fb_info */
   if (info->load == NULL && info->store == NULL)
      return;

   for (unsigned rt = 0; rt < info->fb->rt_count; rt++) {
      fbinfo->rts[rt] = (struct pan_fb_color_attachment) {
         .view = info->store->rts[rt].iview,
         .clear = info->load->rts[rt].border_load == PAN_FB_LOAD_CLEAR ||
                  info->load->rts[rt].in_bounds_load == PAN_FB_LOAD_CLEAR,
         .preload = info->load->rts[rt].in_bounds_load == PAN_FB_LOAD_IMAGE,
         .discard = !info->store->rts[rt].store,
      };

      if (fbinfo->rts[rt].clear) {
         pan_pack_color(GENX(pan_blendable_formats),
                        fbinfo->rts[rt].clear_value,
                        &info->load->rts[rt].clear.color,
                        info->fb->rt_formats[rt],
                        false /* dithered */);
      }
   }

   if (info->fb->z_format != PIPE_FORMAT_NONE) {
      fbinfo->zs.view.zs = info->store->zs.iview;
      fbinfo->zs.clear.z = info->load->z.border_load == PAN_FB_LOAD_CLEAR ||
                           info->load->z.in_bounds_load == PAN_FB_LOAD_CLEAR;
      fbinfo->zs.preload.z = info->load->z.in_bounds_load == PAN_FB_LOAD_IMAGE;
      fbinfo->zs.discard.z = !info->store->zs.store;
      if (fbinfo->zs.clear.z)
         fbinfo->zs.clear_value.depth = info->load->z.clear.depth;
   }

   if (info->fb->s_format != PIPE_FORMAT_NONE) {
      fbinfo->zs.view.s = info->store->s.iview;
      fbinfo->zs.clear.s = info->load->s.border_load == PAN_FB_LOAD_CLEAR ||
                           info->load->s.in_bounds_load == PAN_FB_LOAD_CLEAR;
      fbinfo->zs.preload.s = info->load->s.in_bounds_load == PAN_FB_LOAD_IMAGE;
      fbinfo->zs.discard.s = !info->store->s.store;
      if (fbinfo->zs.clear.s)
         fbinfo->zs.clear_value.stencil = info->load->s.clear.stencil;
   }
}

#if PAN_ARCH >= 5
static bool
target_has_clear(const struct pan_fb_load_target *target)
{
   return target->in_bounds_load == PAN_FB_LOAD_CLEAR ||
          target->border_load == PAN_FB_LOAD_CLEAR;
}

static enum mali_msaa
translate_msaa_copy_op(const struct pan_fb_layout *fb,
                       const struct pan_image_view *iview,
                       enum pan_fb_msaa_copy_op msaa)
{
   switch (msaa) {
   case PAN_FB_MSAA_COPY_ALL:
      assert(pan_image_view_get_nr_samples(iview) == fb->sample_count);
      if (fb->sample_count > 1)
         return MALI_MSAA_LAYERED;
      else
         return MALI_MSAA_SINGLE;

   case PAN_FB_MSAA_COPY_SINGLE:
   case PAN_FB_MSAA_COPY_SAMPLE_0:
      return MALI_MSAA_SINGLE;

   case PAN_FB_MSAA_COPY_AVERAGE:
      /* It must be a blendable format or the hardware can't average */
      assert(GENX(pan_blendable_format_from_pipe_format)(iview->format)->internal);
      if (fb->sample_count > 1)
         return MALI_MSAA_AVERAGE;
      else
         return MALI_MSAA_SINGLE;

   default:
      UNREACHABLE("Unsupported MSAA copy op");
   }
}

struct pan_fb_clean_tile {
   uint8_t rts;
   bool zs, s;
};

static bool
pan_fb_load_target_always(const struct pan_fb_load_target *target)
{
   if (target->in_bounds_load == PAN_FB_LOAD_NONE &&
       target->border_load == PAN_FB_LOAD_NONE)
      return false;

   return target->always;
}

static bool
pan_fb_store_target_always(const struct pan_fb_store_target *target)
{
   return target->store && target->always;
}

static struct pan_fb_clean_tile
pan_fb_get_clean_tile(const struct pan_fb_desc_info *info)
{
   const struct pan_fb_layout *fb = info->fb;
   const struct pan_fb_load *load = info->load;
   const struct pan_fb_store *store = info->store;

   struct pan_fb_clean_tile ct = { };

   for (unsigned rt = 0; rt < fb->rt_count; rt++) {
      if (fb->rt_formats[rt] == PIPE_FORMAT_NONE)
         continue;

      if ((load && pan_fb_load_target_always(&load->rts[rt])) ||
          (store && pan_fb_store_target_always(&store->rts[rt])))
         ct.rts |= BITFIELD_BIT(rt);

      if (store && store->rts[rt].store) {
         const struct pan_image *img =
            pan_image_view_get_color_plane(store->rts[rt].iview).image;
         if (GENX(pan_force_clean_write_on)(img, fb->tile_size_px))
            ct.rts |= BITFIELD_BIT(rt);
      }
   }

   const bool z_always_load = load && pan_fb_load_target_always(&load->z);
   const bool s_always_load = load && pan_fb_load_target_always(&load->s);
   const bool zs_always_store = store && pan_fb_store_target_always(&store->zs);
   const bool s_always_store = store && pan_fb_store_target_always(&store->s);

   if (fb->z_format != PIPE_FORMAT_NONE) {
      ct.zs = z_always_load || zs_always_store;

      if (store && store->zs.store) {
         const struct pan_image *img =
            pan_image_view_get_zs_plane(store->zs.iview).image;
         assert(util_format_get_depth_bits(img->props.format) ==
                util_format_get_depth_bits(fb->z_format));
         assert(util_format_get_depth_bits(store->zs.iview->format) ==
                util_format_get_depth_bits(fb->z_format));
         const struct util_format_description *zs_fmt_desc =
            util_format_description(img->props.format);

         /* If ZS writes stencil, we have to also include stencil loads */
         if (util_format_has_stencil(zs_fmt_desc) && s_always_load)
            ct.zs = true;

         if (GENX(pan_force_clean_write_on)(img, fb->tile_size_px))
            ct.zs = true;
      }
   }

   if (fb->s_format != PIPE_FORMAT_NONE) {
      ct.s = s_always_load || s_always_store;

      if (store && store->s.store) {
         const struct pan_image *img =
            pan_image_view_get_s_plane(store->s.iview).image;
         if (GENX(pan_force_clean_write_on)(img, fb->tile_size_px))
            ct.s = true;
      }
   }

   return ct;
}

static void
emit_zs_crc_desc(const struct pan_fb_desc_info *info,
                 const struct pan_fb_clean_tile ct,
                 struct mali_zs_crc_extension_packed *zs_crc)
{
   const struct pan_fb_layout *fb = info->fb;
   const struct pan_fb_store *store = info->store;

   pan_pack(zs_crc, ZS_CRC_EXTENSION, cfg) {
      if (store && store->zs.store) {
         cfg.zs.msaa = translate_msaa_copy_op(fb, store->zs.iview,
                                              store->zs.msaa);

#if PAN_ARCH >= 6
         cfg.zs.clean_tile_write_enable = ct.zs;
#else
         cfg.zs.clean_pixel_write_enable = ct.zs;
#endif

      }

      if (store && store->s.store) {
         cfg.s.msaa = translate_msaa_copy_op(fb, store->s.iview,
                                             store->s.msaa);

#if PAN_ARCH >= 6
         cfg.s.clean_tile_write_enable = ct.s;
#else
         cfg.s.clean_pixel_write_enable = ct.s;
#endif
      }

      /* TODO CRC */
   }

   if (store && store->zs.store) {
      const struct pan_image_view *iview = store->zs.iview;
      const struct pan_mod_handler *mod_handler =
         pan_image_view_get_zs_plane(iview).image->mod_handler;

      assert(info->layer < pan_image_view_get_layer_count(iview));
      const struct pan_attachment_info att = {
         .iview = iview,
         .layer_or_z_slice = iview->first_layer + info->layer,
         .fb_tile_size_px = fb->tile_size_px,
      };

      struct mali_zs_crc_extension_packed zs_part;
      mod_handler->emit_zs_attachment(&att, &zs_part);
      pan_merge(zs_crc, &zs_part, ZS_CRC_EXTENSION);
   }

   if (store && store->s.store) {
      const struct pan_image_view *iview = store->s.iview;
      const struct pan_mod_handler *mod_handler =
         pan_image_view_get_s_plane(iview).image->mod_handler;

      assert(info->layer < pan_image_view_get_layer_count(iview));
      const struct pan_attachment_info att = {
         .iview = iview,
         .layer_or_z_slice = iview->first_layer + info->layer,
         .fb_tile_size_px = fb->tile_size_px,
      };

      struct mali_zs_crc_extension_packed s_part;
      mod_handler->emit_s_attachment(&att, &s_part);
      pan_merge(zs_crc, &s_part, ZS_CRC_EXTENSION);
   }

   /* TODO: CRC */
}

static void
emit_rgb_rt_desc(const struct pan_fb_desc_info *info,
                 const struct pan_fb_clean_tile ct,
                 unsigned rt, uint32_t tile_rt_offset_B,
                 struct mali_rgb_render_target_packed *rgb_rt)
{
   const struct pan_fb_layout *fb = info->fb;
   const struct pan_fb_load *load = info->load;
   const struct pan_fb_store *store = info->store;

   if (fb->rt_formats[rt] == PIPE_FORMAT_NONE) {
      pan_pack(rgb_rt, RGB_RENDER_TARGET, cfg) {
         /* Place non-existent targets at the end.  Hardware bounds
          * checking should just discard writes and zero reads.
          */
         cfg.internal_buffer_offset = fb->tile_rt_alloc_B;
         cfg.internal_format = MALI_COLOR_BUFFER_INTERNAL_FORMAT_R8G8B8A8;
         cfg.write_enable = false;
#if PAN_ARCH >= 7
         cfg.writeback_block_format = MALI_BLOCK_FORMAT_TILED_U_INTERLEAVED;
#endif
      }
      return;
   }

   pan_pack(rgb_rt, RGB_RENDER_TARGET, cfg) {
      cfg.internal_buffer_offset = tile_rt_offset_B;
      cfg.dithering_enable = true;

      if (store && store->rts[rt].store) {
         cfg.writeback_msaa =
            translate_msaa_copy_op(fb, store->rts[rt].iview,
                                   store->rts[rt].msaa);
      }

#if PAN_ARCH >= 6
      cfg.clean_tile_write_enable = !!(ct.rts & BITFIELD_BIT(rt));
#else
      cfg.clean_pixel_write_enable = !!(ct.rts & BITFIELD_BIT(rt));
#endif

      if (load && target_has_clear(&load->rts[rt])) {
         uint32_t packed[4] = {};
         pan_pack_color(GENX(pan_blendable_formats), packed,
                        &load->rts[rt].clear.color, fb->rt_formats[rt],
                        false /* dithered */);

         cfg.clear = (struct MALI_RT_CLEAR) {
            .color_0 = packed[0],
            .color_1 = packed[1],
            .color_2 = packed[2],
            .color_3 = packed[3],
         };
      }
   }

   struct mali_render_target_packed desc;
   if (store && store->rts[rt].store) {
      const struct pan_image_view *iview = store->rts[rt].iview;
      const struct pan_mod_handler *mod_handler =
         pan_image_view_get_color_plane(iview).image->mod_handler;

      assert(info->layer < pan_image_view_get_layer_count(iview));
      const struct pan_attachment_info att = {
         .iview = iview,
         .layer_or_z_slice = iview->first_layer + info->layer,
         .fb_tile_size_px = fb->tile_size_px,
      };

      mod_handler->emit_color_attachment(&att, &desc);
   } else {
      GENX(pan_emit_default_color_attachment)(fb->rt_formats[rt], &desc);
   }
   pan_merge(rgb_rt, &desc, RGB_RENDER_TARGET);
}

#if PAN_ARCH >= 6
/* All GPUs starting from Bifrost are affected by issue TSIX-2033:
 *
 *      Forcing clean_tile_writes breaks INTERSECT readbacks
 *
 * To workaround, use the pre-frame shader mode ALWAYS instead of INTERSECT if
 * clean_tile_write_enable is set on either one of the color, depth or stencil
 * buffers. Since INTERSECT is a hint that the hardware may ignore, this
 * cannot affect correctness, only performance. */

static enum mali_pre_post_frame_shader_mode
pan_fix_frame_shader_mode(enum mali_pre_post_frame_shader_mode mode,
                          bool force_clean_tile)
{
   if (force_clean_tile && mode == MALI_PRE_POST_FRAME_SHADER_MODE_INTERSECT)
      return MALI_PRE_POST_FRAME_SHADER_MODE_ALWAYS;
   else
      return mode;
}
#endif

uint32_t
GENX(pan_emit_fb_desc)(const struct pan_fb_desc_info *info, void *out)
{
   const struct pan_fb_layout *fb = info->fb;
   const struct pan_fb_load *load = info->load;
   const struct pan_fb_store *store = info->store;
   const struct pan_fb_clean_tile ct = pan_fb_get_clean_tile(info);

   const bool has_zs_crc_ext = pan_fb_has_zs(fb);

   struct mali_framebuffer_packed fbd = {};

#if PAN_ARCH <= 5
   GENX(pan_emit_tls)(info->tls,
      pan_section_ptr(&fbd, FRAMEBUFFER, LOCAL_STORAGE));
#endif

   pan_section_pack(&fbd, FRAMEBUFFER, PARAMETERS, cfg) {
#if PAN_ARCH >= 6
      cfg.pre_frame_0 = pan_fix_frame_shader_mode(info->frame_shaders.modes[0],
                                                  ct.rts || ct.zs || ct.s);
      cfg.pre_frame_1 = pan_fix_frame_shader_mode(info->frame_shaders.modes[1],
                                                  ct.rts || ct.zs || ct.s);
      cfg.post_frame = info->frame_shaders.modes[2];
      cfg.frame_shader_dcds = info->frame_shaders.dcd_pointer;

      cfg.sample_locations = info->sample_pos_array_pointer;
#endif

#if PAN_ARCH >= 13
      /* Enabling prepass without pipelineing is generally not good for
       * performance, so disable HSR in that case.
       */
      cfg.hsr_prepass_enable = info->allow_hsr_prepass &&
                               pan_fb_can_pipeline_zs(fb);
      cfg.hsr_prepass_interleaving_enable = pan_fb_can_pipeline_zs(fb);
      cfg.hsr_prepass_filter_enable = true;
      cfg.hsr_hierarchical_optimizations_enable = true;
#endif

#if PAN_ARCH >= 9
      /* internal_layer_index is used to select the right primitive list in
       * the tiler context, and frame_arg is the value that's passed to the
       * fragment shader through r62-r63, which we use to pass gl_Layer. Since
       * the layer_idx only takes 8-bits, we might use the extra 56-bits we
       * have in frame_argument to pass other information to the fragment
       * shader at some point.
       */
      assert(info->layer >= info->tiler_ctx->valhall.layer_offset);
      cfg.internal_layer_index =
         info->layer - info->tiler_ctx->valhall.layer_offset;
      cfg.frame_argument = info->layer;
#endif

      cfg.width = fb->width_px;
      cfg.height = fb->height_px;

      const struct pan_fb_bbox fb_area_px =
         pan_fb_bbox_from_xywh(0, 0, info->fb->width_px, info->fb->height_px);
      assert(pan_fb_bbox_is_valid(info->fb->tiling_area_px));
      const struct pan_fb_bbox bbox_px =
         pan_fb_bbox_clamp(info->fb->tiling_area_px, fb_area_px);
      cfg.bound_min_x = bbox_px.min_x;
      cfg.bound_min_y = bbox_px.min_y;
      cfg.bound_max_x = bbox_px.max_x;
      cfg.bound_max_y = bbox_px.max_y;

      cfg.sample_count = fb->sample_count;
      cfg.sample_pattern = pan_sample_pattern(fb->sample_count);

      /* Ensure we cover the samples on the edge for 16x MSAA */
      cfg.tie_break_rule = fb->sample_count == 16 ?
         MALI_TIE_BREAK_RULE_MINUS_180_OUT_0_IN :
         MALI_TIE_BREAK_RULE_MINUS_180_IN_0_OUT;

      cfg.effective_tile_size = fb->tile_size_px;

#if PAN_ARCH >= 9
      cfg.point_sprite_coord_origin_max_y = info->sprite_coord_origin_max_y;
      cfg.first_provoking_vertex = info->provoking_vertex_first;
#endif

      assert(fb->rt_count > 0);
      cfg.render_target_count = fb->rt_count;
      cfg.color_buffer_allocation = fb->tile_rt_alloc_B;

      if (fb->s_format != PIPE_FORMAT_NONE) {
         cfg.s_clear = load && target_has_clear(&load->s) ?
                       load->s.clear.stencil : 0;
         cfg.s_write_enable = store && store->s.store;
      }

      if (fb->z_format != PIPE_FORMAT_NONE) {
         cfg.z_internal_format = pan_get_z_internal_format(fb->z_format);
         cfg.z_clear = load && target_has_clear(&load->z) ?
                       load->z.clear.depth : 0;
         cfg.z_write_enable = store && store->zs.store;
      } else {
         /* Default to 24 bit depth if there's no surface. */
         cfg.z_internal_format = MALI_Z_INTERNAL_FORMAT_D24;

         /* If we want a stencil store and we don't have depth, that should
          * happen as a stencil store, not combined depth/stencil.
          */
         assert(!store || !store->zs.store);
      }

      cfg.has_zs_crc_extension = has_zs_crc_ext;

#if PAN_ARCH >= 6
      cfg.tiler = PAN_ARCH >= 9 ? info->tiler_ctx->valhall.desc
                                : info->tiler_ctx->bifrost.desc;
#endif
   }

#if PAN_ARCH <= 5
   /* TODO: Midgard tiler */
   assert(info->tiler_ctx == NULL);

   /* All weights set to 0, nothing to do here */
   pan_section_pack(&fbd, FRAMEBUFFER, TILER_WEIGHTS, w);
#endif

   memcpy(out, &fbd, sizeof(fbd));
   out += sizeof(fbd);

   if (has_zs_crc_ext) {
      struct mali_zs_crc_extension_packed zs_crc;
      emit_zs_crc_desc(info, ct, &zs_crc);
      memcpy(out, &zs_crc, sizeof(zs_crc));
      out += sizeof(zs_crc);
   }

   uint32_t tile_rt_offset_B = 0;
   for (unsigned rt = 0; rt < fb->rt_count; rt++) {
      struct mali_rgb_render_target_packed rgb_rt;
      emit_rgb_rt_desc(info, ct, rt, tile_rt_offset_B, &rgb_rt);
      memcpy(out, &rgb_rt, sizeof(rgb_rt));
      out += sizeof(rgb_rt);

      if (fb->rt_formats[rt] != PIPE_FORMAT_NONE) {
         tile_rt_offset_B += pan_bytes_per_pixel_tib(fb->rt_formats[rt]) *
                             fb->tile_size_px * fb->sample_count;
      }
   }
   assert(tile_rt_offset_B <= fb->tile_rt_alloc_B);

   struct mali_framebuffer_pointer_packed tag;
   pan_pack(&tag, FRAMEBUFFER_POINTER, cfg) {
      cfg.zs_crc_extension_present = has_zs_crc_ext;
      cfg.render_target_count = fb->rt_count;
   }
   return tag.opaque[0];
}
#endif
