/*
 * Copyright (C) 2026 Collabora, Ltd.
 * SPDX-License-Identifier: MIT
 */

#ifndef __PAN_FB_H
#define __PAN_FB_H

#include "genxml/gen_macros.h"
#include "util/format/u_formats.h"
#include "compiler/shader_enums.h"

struct nir_shader;
struct nir_shader_compiler_options;
struct pan_fb_info;
struct pan_image_view;

#define PAN_MAX_RTS 8

/** Bounding box used by the framebuffer abstraction */
struct pan_fb_bbox {
   /** Minimum x/y value */
   uint16_t min_x, min_y;

   /** Maximum x/y value
    *
    * Like the hardware, the maximums here are inclusive.  A pixel is
    * in-bounds if min_x <= x <= max_x and min_y <= y <= max_y.
    */
   uint16_t max_x, max_y;
};

static inline bool
pan_fb_bbox_is_valid(struct pan_fb_bbox b)
{
   return b.min_x <= b.max_x && b.min_y <= b.max_y;
}

static inline bool
pan_fb_bbox_contains_bbox(struct pan_fb_bbox a, struct pan_fb_bbox b)
{
   return a.min_x <= b.min_x && b.max_x <= a.max_x &&
          a.min_y <= b.min_y && b.max_y <= a.max_y;
}

static inline bool
pan_fb_bbox_equal(struct pan_fb_bbox a, struct pan_fb_bbox b)
{
   return a.min_x == b.min_x && b.max_x == a.max_x &&
          a.min_y == b.min_y && b.max_y == a.max_y;
}

static inline struct pan_fb_bbox
pan_fb_bbox_clamp(struct pan_fb_bbox a, struct pan_fb_bbox b)
{
   assert(pan_fb_bbox_is_valid(a) && pan_fb_bbox_is_valid(a));
   return (struct pan_fb_bbox) {
      .min_x = CLAMP(a.min_x, b.min_x, b.max_x),
      .min_y = CLAMP(a.min_y, b.min_y, b.max_y),
      .max_x = CLAMP(a.max_x, b.min_x, b.max_x),
      .max_y = CLAMP(a.max_y, b.min_y, b.max_y),
   };
}

static inline struct pan_fb_bbox
pan_fb_bbox_align(struct pan_fb_bbox b, uint32_t align_x, uint32_t align_y)
{
   assert(util_is_power_of_two_nonzero(align_x));
   assert(util_is_power_of_two_nonzero(align_y));
   assert(align_x <= UINT16_MAX && align_y <= UINT16_MAX);

   b.min_x = ROUND_DOWN_TO(b.min_x, align_x);
   b.min_y = ROUND_DOWN_TO(b.min_y, align_y);
   b.max_x = align((uint32_t)b.max_x + 1, align_x) - 1;
   b.max_y = align((uint32_t)b.max_y + 1, align_y) - 1;

   return b;
}

static inline struct pan_fb_bbox
pan_fb_bbox_from_xywh(uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
   assert(x + w > x && x + w - 1 <= UINT16_MAX);
   assert(y + h > y && y + h - 1 <= UINT16_MAX);
   return (struct pan_fb_bbox) {
      .min_x = x,
      .min_y = y,
      .max_x = x + w - 1,
      .max_y = y + h - 1,
   };
}

/** Framebuffer layout
 *
 * This describes the layout of framebuffer in tile memory as well as various
 * properties that are properties of the framebuffer itself, nit any of the
 * image views attached to it.
 */
struct pan_fb_layout {
   /** Dimensions of the framebuffer itself, in pixels */
   uint32_t width_px, height_px;

   /** The render area
    *
    * This is the API render area (as defined by Vulkan) and may be smaller
    * than the actual bounding box of the framebuffer.  The in_bounds_load
    * and border_load members of pan_fb_load_target are relative to the
    * render area.
    */
   struct pan_fb_bbox render_area_px;

   /** The tiling area
    *
    * This is based on the render area but may be larger to accomodate tiling
    * restrictions.
    */
   struct pan_fb_bbox tiling_area_px;

   /** Sample count for this framebuffer */
   uint8_t sample_count;

   /** Color render target formats
    *
    * If a render target is unused, set it to PIPE_FORMAT_NONE.
    */
   uint8_t rt_count;
   enum pipe_format rt_formats[PAN_MAX_RTS];

   /** Depth format for this framebuffer, or PIPE_FORMAT_NONE */
   enum pipe_format z_format;

   /** Stencil format for this framebuffer, or PIPE_FORMAT_NONE */
   enum pipe_format s_format;

   /** Amount of space reserved for PLS, in bytes.
    *
    * This may overlap the color render targets in tile memory.
    */
   uint16_t pls_size_B;

   uint32_t tile_rt_budget_B;
   uint32_t tile_z_budget_B;

   /** Optimal tile buffer size, in pixels
    *
    * This must be a power of two.
    */
   unsigned tile_size_px;

   /** Amount of tile memory allocated for color render targets */
   unsigned tile_rt_alloc_B;
};

static inline bool
pan_fb_has_zs(const struct pan_fb_layout *fb)
{
   /* An intentional choice is made here to base has_zs on the formats in
    * the pan_fb_layout and not the stores being done in the end.  PanVK in
    * particular relies on the size of the final framebuffer descriptor being
    * the same for all the different variants with incremental rendering.
    */
   return fb->z_format != PIPE_FORMAT_NONE ||
          fb->s_format != PIPE_FORMAT_NONE;
}

static inline bool
pan_fb_is_fully_covered(const struct pan_fb_layout *fb)
{
   const struct pan_fb_bbox fb_area_px =
      pan_fb_bbox_from_xywh(0, 0, fb->width_px, fb->height_px);
   return pan_fb_bbox_equal(fb->render_area_px, fb_area_px);
}

static inline bool
pan_fb_has_partial_tiles(const struct pan_fb_layout *fb)
{
   assert(pan_fb_bbox_contains_bbox(fb->tiling_area_px, fb->render_area_px));
   return !pan_fb_bbox_equal(fb->tiling_area_px, fb->render_area_px);
}

#ifdef PAN_ARCH
void GENX(pan_select_fb_tile_size)(struct pan_fb_layout *fb);
#endif

enum ENUM_PACKED pan_fb_load_op {
   PAN_FB_LOAD_NONE = 0,
   PAN_FB_LOAD_CLEAR,
   PAN_FB_LOAD_IMAGE,
   PAN_FB_LOAD_OP_COUNT,
};

/** Describes how MSAA is handled when copying
 *
 * The source or destination may or may not be multisampled independently and
 * this controls how that is handled in the copy.
 */
enum ENUM_PACKED pan_fb_msaa_copy_op {
   /** Copies all samples of the source to the destination
    *
    * This requires that the source and destination sample counts be the same.
    * If the source and destination are both single-sampled, this is equivalent
    * as PAN_FB_MSAA_COPY_SINGLE or PAN_FB_MSAA_COPY_SAMPLE_0.
    */
   PAN_FB_MSAA_COPY_ALL = 0,

   /** Copies the single sample from the source to the destination
    *
    * This requires that the source be single-sampled.  Semantically, this is
    * the same as PAN_FB_MSAA_COPY_SAMPLE_0, but it's useful to make this
    * distinction in shader keys so single and multi-sampled sources can be
    * differentiated.
    */
   PAN_FB_MSAA_COPY_SINGLE,

   /** Copies assuming all source samples are identical
    *
    * The actual copy can use any resolve mode because we know a priori that
    * all samples are identical.  All resolve modes yield the same result.
    */
   PAN_FB_MSAA_COPY_IDENTICAL,

   /** Copies sample 0 from the source to all samples in the destination */
   PAN_FB_MSAA_COPY_SAMPLE_0,

   /** Copies the average of the samples in the source to the destination */
   PAN_FB_MSAA_COPY_AVERAGE,

   /** Copies the minimum of the samples in the source to the destination */
   PAN_FB_MSAA_COPY_MIN,

   /** Copies the maximum of the samples in the source to the destination */
   PAN_FB_MSAA_COPY_MAX,

   PAN_FB_MSAA_COPY_OP_COUNT,
};

struct pan_fb_load_target {
   /** Load op for pixels inside the bounding box */
   enum pan_fb_load_op in_bounds_load;

   /** Load op for border (out of bounds) pixels
    *
    * This should almost always be LOAD_OP_LOAD, unless you have a very good
    * reason to make a different choice.
    */
   enum pan_fb_load_op border_load;

   /** Always load, even if no primitives intersect the tile
    *
    * This should usually be set for clears.
    */
   bool always;

   /** MSAA mode used when load op is IMAGE */
   enum pan_fb_msaa_copy_op msaa;

   /* Image view to load from, or NULL if neither load op is IMAGE */
   const struct pan_image_view *iview;

   /** Clear value, if either load op is CLEAR */
   union {
      union pipe_color_union color;
      float depth;
      uint8_t stencil;
   } clear;
};

static inline struct pan_fb_load_target
pan_fb_load_iview(const struct pan_image_view *iview)
{
   return (struct pan_fb_load_target) {
      .in_bounds_load = PAN_FB_LOAD_IMAGE,
      .border_load = PAN_FB_LOAD_IMAGE,
      .msaa = PAN_FB_MSAA_COPY_ALL,
      .iview = iview,
   };
}

struct pan_fb_load {
   /** Color render target loads */
   struct pan_fb_load_target rts[PAN_MAX_RTS];

   /** Depth target load */
   struct pan_fb_load_target z;

   /** Stencil target load */
   struct pan_fb_load_target s;
};

static inline bool
pan_fb_has_image_load(const struct pan_fb_load *load, bool include_border)
{
   if (load->z.in_bounds_load == PAN_FB_LOAD_IMAGE ||
       load->s.in_bounds_load == PAN_FB_LOAD_IMAGE)
      return true;

   if (include_border &&
       (load->z.border_load == PAN_FB_LOAD_IMAGE ||
        load->s.border_load == PAN_FB_LOAD_IMAGE))
      return true;

   for (unsigned rt = 0; rt < PAN_MAX_RTS; rt++) {
      if (load->rts[rt].in_bounds_load == PAN_FB_LOAD_IMAGE)
         return true;
      if (include_border && load->rts[rt].border_load == PAN_FB_LOAD_IMAGE)
         return true;
   }

   return false;
}

/** Describes a resolve operation
 *
 * This describes the source data for the resolve operation.  The way in which
 * MSAA is handled in the source data is defined by the pan_fb_msaa_copy_op.
 */
enum ENUM_PACKED pan_fb_resolve_op {
   /** Do nothing
    *
    * In this mode, the framebuffer is left unaffected by the resolve.  If
    * necessary (sometimes required for Z/S), the old value will be read and
    * written back to ensure the final value is the same.
    */
   PAN_FB_RESOLVE_NONE = 0,

   /** Load from the specified image view */
   PAN_FB_RESOLVE_IMAGE,

   /** Load from color render target 0 */
   PAN_FB_RESOLVE_RT_0,
   PAN_FB_RESOLVE_RT_1,
   PAN_FB_RESOLVE_RT_2,
   PAN_FB_RESOLVE_RT_3,
   PAN_FB_RESOLVE_RT_4,
   PAN_FB_RESOLVE_RT_5,
   PAN_FB_RESOLVE_RT_6,
   PAN_FB_RESOLVE_RT_7,

   /** Load from the depth target
    *
    * From the perspective of resolve ops, Z/S are always separate
    */
   PAN_FB_RESOLVE_Z,

   /** Load from the stencil target
    *
    * From the perspective of resolve ops, Z/S are always separate
    */
   PAN_FB_RESOLVE_S,
   PAN_FB_RESOLVE_OP_COUNT,
};

static_assert(PAN_FB_RESOLVE_Z == PAN_FB_RESOLVE_RT_0 + PAN_MAX_RTS,
              "There are PAN_MAX_RTS many RTs");

#define PAN_FB_RESOLVE_RT(rt) \
   (assert(0 <= (rt) && (rt) < PAN_MAX_RTS), (PAN_FB_RESOLVE_RT_0 + (rt)))

/** Describes a resovle operation on a given target
 *
 * For each side of the render area (in-bounds or border), this defines both
 * a source to copy from (the resolve op) and a MSAA copy op to apply as part
 * of the copy.  In the common case, a resolve op will read from itself but
 * that is not strictly a requirement.  Any resolve op can read from any
 * render target.
 */
struct pan_fb_resolve_target {
   struct pan_fb_resolve_op_msaa {
      enum pan_fb_resolve_op resolve;
      enum pan_fb_msaa_copy_op msaa;
   } in_bounds, border;

   /** For PAN_FB_RESOLVE_IMAGE the image view to load from */
   const struct pan_image_view *iview;
};

/** Describes a resolve operation
 *
 * A resolve operation is implemented as a post-frame shader (see
 * pan_fb_resolve_shader_key_fill) and does a copy from render targets to
 * render targets, applying MSAA copy ops along the way.
 */
struct pan_fb_resolve {
   struct pan_fb_resolve_target rts[PAN_MAX_RTS];
   struct pan_fb_resolve_target z, s;
};

struct pan_fb_store_target {
   /** Whether or not to do a store */
   bool store;

   /** Always store, even if no primitives intersect the tile
    *
    * clean_tile_write_enable will be set if either store.always is set or
    * load.always is set for the corresponding load[s].
    */
   bool always;

   /** MSAA mode to use when copying to the destination image
    *
    * This can only be set to AVERAGE for blendable color formats.
    */
   enum pan_fb_msaa_copy_op msaa;

   /** Image view to store to */
   const struct pan_image_view *iview;
};

static inline struct pan_fb_store_target
pan_fb_store_iview(const struct pan_image_view *iview)
{
   return (struct pan_fb_store_target) {
      .store = true,
      .msaa = PAN_FB_MSAA_COPY_ALL,
      .iview = iview,
   };
}

static inline struct pan_fb_store_target
pan_fb_always_store_iview_s0(const struct pan_image_view *iview)
{
   return (struct pan_fb_store_target) {
      .store = true,
      .always = true,
      .msaa = PAN_FB_MSAA_COPY_SAMPLE_0,
      .iview = iview,
   };
}

struct pan_fb_store {
   /* Color render target stores */
   struct pan_fb_store_target rts[PAN_MAX_RTS];

   /* Depth/stencil target store
    *
    * If the attached image view has a combined depth/stencil format, both
    * depth and stencil will be written.
    */
   struct pan_fb_store_target zs;

   /* Stencil target store */
   struct pan_fb_store_target s;
};

#ifdef PAN_ARCH
void GENX(pan_align_fb_tiling_area)(struct pan_fb_layout *fb,
                                    const struct pan_fb_store *store);

void GENX(pan_fb_fold_resolve_into_store)(const struct pan_fb_layout *fb,
                                          struct pan_fb_resolve *resolve,
                                          struct pan_fb_store *store);
#endif

struct pan_fb_frame_shaders {
   uint64_t dcd_pointer;
   uint8_t modes[3];
};

struct pan_fb_desc_info {
   const struct pan_fb_layout *fb;
   const struct pan_fb_load *load;
   const struct pan_fb_store *store;

   struct pan_fb_frame_shaders frame_shaders;

   uint64_t sample_pos_array_pointer;

   /* Only used on Valhal */
   bool sprite_coord_origin_max_y;
   bool provoking_vertex_first;
   bool allow_hsr_prepass;

   uint16_t layer;

   const struct pan_tls_info *tls;
   const struct pan_tiler_context *tiler_ctx;
};

#ifdef PAN_ARCH
void GENX(pan_fill_fb_info)(const struct pan_fb_desc_info *info,
                            struct pan_fb_info *fbinfo);

uint32_t GENX(pan_emit_fb_desc)(const struct pan_fb_desc_info *info,
                                void *out);
#endif

enum ENUM_PACKED pan_fb_shader_op {
   /** Preserves the current fragment contents
    *
    * The MSAA mode is ignored and, if a load does need to occur to preserve
    * the current fragment contents, PAN_FB_MSAA_COPY_ALL is implied.
    *
    * This is only allowed in resolve shaders so this can always be replaced
    * with a discard.
    */
   PAN_FB_SHADER_PRESERVE = 0,

   /** Do whatever is the fastest (lowest bandwidth)
    *
    * This could be a CLEAR or PRESERVE or just write zeros.  It will never
    * write actual random data but the exact behavior may depend on other bits
    * in the shader key.
    */
   PAN_FB_SHADER_DONT_CARE,

   /** Loads the clear color
    *
    * This is only allowed in preload shaders and the clear will also be done
    * in hardware so it can always be replaced with a discard.
    */
   PAN_FB_SHADER_LOAD_CLEAR,

   /** Loads from the image bound at the corresponding gl_frag_result */
   PAN_FB_SHADER_LOAD_IMAGE,

   PAN_FB_SHADER_COPY_RT_0,
   PAN_FB_SHADER_COPY_RT_1,
   PAN_FB_SHADER_COPY_RT_2,
   PAN_FB_SHADER_COPY_RT_3,
   PAN_FB_SHADER_COPY_RT_4,
   PAN_FB_SHADER_COPY_RT_5,
   PAN_FB_SHADER_COPY_RT_6,
   PAN_FB_SHADER_COPY_RT_7,
   PAN_FB_SHADER_COPY_Z,
   PAN_FB_SHADER_COPY_S,

   PAN_FB_SHADER_OP_COUNT,
};

static_assert(PAN_FB_SHADER_COPY_Z == PAN_FB_SHADER_COPY_RT_0 + PAN_MAX_RTS,
              "We should have one PAN_FB_SHADER_COPY_RT_N per RT");

#define PAN_FB_SHADER_COPY_RT(rt) \
   (assert(0 <= (rt) && (rt) < PAN_MAX_RTS), (PAN_FB_SHADER_COPY_RT_0 + (rt)))

static inline bool
pan_fb_shader_op_can_discard(enum pan_fb_shader_op op)
{
   return op == PAN_FB_SHADER_PRESERVE ||
          op == PAN_FB_SHADER_DONT_CARE ||
          op == PAN_FB_SHADER_LOAD_CLEAR;
}

enum ENUM_PACKED pan_fb_shader_data_type {
   PAN_FB_SHADER_DATA_TYPE_F32 = 0,
   PAN_FB_SHADER_DATA_TYPE_I32,
   PAN_FB_SHADER_DATA_TYPE_U32,
   PAN_FB_SHADER_DATA_TYPE_COUNT,
};

static_assert(PAN_FB_SHADER_OP_COUNT <= (1 << 4),
              "pan_fb_shader_op fits in 4 bits");
static_assert(PAN_FB_MSAA_COPY_OP_COUNT <= (1 << 3),
              "pan_fb_msaa_copy_op fits in 3 bits");
static_assert(PAN_FB_SHADER_DATA_TYPE_COUNT <= (1 << 2),
              "pan_fb_shader_data_type fits in 2 bits");

/* Asserts for glsl_sampler_dim glsl_base_type have to be runtime because
 * there is no MAX value we can use.
 */

PRAGMA_DIAGNOSTIC_PUSH
PRAGMA_DIAGNOSTIC_ERROR(-Wpadded)
struct pan_fb_shader_key_target {
   uint32_t in_bounds_op : 4;
   uint32_t border_op : 4;
   uint32_t in_bounds_msaa : 3;
   uint32_t border_msaa : 3;
   uint32_t sample0_only : 1;
   uint32_t image_dim : 2;
   uint32_t image_is_array : 1;
   uint32_t image_samples_log2 : 3;
   uint32_t data_type : 2;
   uint32_t pad : 9;
};
PRAGMA_DIAGNOSTIC_POP
static_assert(sizeof(struct pan_fb_shader_key_target) == 4,
              "This struct has no holes");

/** Whether or not the given target is written by the FB shader
 *
 * GENX(pan_get_fb_shader)() guarantees that if both in_bounds_op and
 * border_op can be discarded, that it does nothing with that target.  This
 * way the full outputs_written can be known from just the shader key.
 */
static inline bool
pan_fb_shader_key_target_written(const struct pan_fb_shader_key_target *target)
{
   return !pan_fb_shader_op_can_discard(target->in_bounds_op) ||
          !pan_fb_shader_op_can_discard(target->border_op);
}

PRAGMA_DIAGNOSTIC_PUSH
PRAGMA_DIAGNOSTIC_ERROR(-Wpadded)
struct pan_fb_shader_key {
   struct pan_fb_shader_key_target rts[PAN_MAX_RTS];
   struct pan_fb_shader_key_target z, s;
   uint16_t z_format;
   uint8_t fb_sample_count;
   uint8_t _pad;
};
PRAGMA_DIAGNOSTIC_POP
static_assert(sizeof(struct pan_fb_shader_key) == 4 * (PAN_MAX_RTS + 3),
              "This struct has no holes");

#ifdef PAN_ARCH
bool GENX(pan_fb_load_shader_key_fill)(struct pan_fb_shader_key *key,
                                       const struct pan_fb_layout *fb,
                                       const struct pan_fb_load *load,
                                       bool zs_prepass);

#if PAN_ARCH >= 6
bool GENX(pan_fb_resolve_shader_key_fill)(struct pan_fb_shader_key *key,
                                          const struct pan_fb_layout *fb,
                                          const struct pan_fb_resolve *resolve);
#endif

struct nir_shader *
GENX(pan_get_fb_shader)(const struct pan_fb_shader_key *key,
                        const struct nir_shader_compiler_options *nir_options);
#endif

#endif /* __PAN_FB_H */
