// Copyright 2009 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <string>
#include <utility>

#include "Common/BitField.h"
#include "Common/BitUtils.h"
#include "Common/CommonTypes.h"
#include "Common/EnumFormatter.h"
#include "Common/Inline.h"

// X.h defines None to be 0 and Always to be 2, which causes problems with some of the enums
#undef None
#undef Always

enum class TextureFormat;
enum class EFBCopyFormat;
enum class TLUTFormat;

#pragma pack(4)

enum
{
  BPMEM_GENMODE = 0x00,
  BPMEM_DISPLAYCOPYFILTER = 0x01,  // 0x01 + 4
  BPMEM_IND_MTXA = 0x06,           // 0x06 + (3 * 3)
  BPMEM_IND_MTXB = 0x07,           // 0x07 + (3 * 3)
  BPMEM_IND_MTXC = 0x08,           // 0x08 + (3 * 3)
  BPMEM_IND_IMASK = 0x0F,
  BPMEM_IND_CMD = 0x10,  // 0x10 + 16
  BPMEM_SCISSORTL = 0x20,
  BPMEM_SCISSORBR = 0x21,
  BPMEM_LINEPTWIDTH = 0x22,
  BPMEM_PERF0_TRI = 0x23,
  BPMEM_PERF0_QUAD = 0x24,
  BPMEM_RAS1_SS0 = 0x25,
  BPMEM_RAS1_SS1 = 0x26,
  BPMEM_IREF = 0x27,
  BPMEM_TREF = 0x28,      // 0x28 + 8
  BPMEM_SU_SSIZE = 0x30,  // 0x30 + (2 * 8)
  BPMEM_SU_TSIZE = 0x31,  // 0x31 + (2 * 8)
  BPMEM_ZMODE = 0x40,
  BPMEM_BLENDMODE = 0x41,
  BPMEM_CONSTANTALPHA = 0x42,
  BPMEM_ZCOMPARE = 0x43,
  BPMEM_FIELDMASK = 0x44,
  BPMEM_SETDRAWDONE = 0x45,
  BPMEM_BUSCLOCK0 = 0x46,
  BPMEM_PE_TOKEN_ID = 0x47,
  BPMEM_PE_TOKEN_INT_ID = 0x48,
  BPMEM_EFB_TL = 0x49,
  BPMEM_EFB_WH = 0x4A,
  BPMEM_EFB_ADDR = 0x4B,
  BPMEM_MIPMAP_STRIDE = 0x4D,
  BPMEM_COPYYSCALE = 0x4E,
  BPMEM_CLEAR_AR = 0x4F,
  BPMEM_CLEAR_GB = 0x50,
  BPMEM_CLEAR_Z = 0x51,
  BPMEM_TRIGGER_EFB_COPY = 0x52,
  BPMEM_COPYFILTER0 = 0x53,
  BPMEM_COPYFILTER1 = 0x54,
  BPMEM_CLEARBBOX1 = 0x55,
  BPMEM_CLEARBBOX2 = 0x56,
  BPMEM_CLEAR_PIXEL_PERF = 0x57,
  BPMEM_REVBITS = 0x58,
  BPMEM_SCISSOROFFSET = 0x59,
  BPMEM_PRELOAD_ADDR = 0x60,
  BPMEM_PRELOAD_TMEMEVEN = 0x61,
  BPMEM_PRELOAD_TMEMODD = 0x62,
  BPMEM_PRELOAD_MODE = 0x63,
  BPMEM_LOADTLUT0 = 0x64,
  BPMEM_LOADTLUT1 = 0x65,
  BPMEM_TEXINVALIDATE = 0x66,
  BPMEM_PERF1 = 0x67,
  BPMEM_FIELDMODE = 0x68,
  BPMEM_BUSCLOCK1 = 0x69,
  BPMEM_TX_SETMODE0 = 0x80,     // 0x80 + 4
  BPMEM_TX_SETMODE1 = 0x84,     // 0x84 + 4
  BPMEM_TX_SETIMAGE0 = 0x88,    // 0x88 + 4
  BPMEM_TX_SETIMAGE1 = 0x8C,    // 0x8C + 4
  BPMEM_TX_SETIMAGE2 = 0x90,    // 0x90 + 4
  BPMEM_TX_SETIMAGE3 = 0x94,    // 0x94 + 4
  BPMEM_TX_SETTLUT = 0x98,      // 0x98 + 4
  BPMEM_TX_SETMODE0_4 = 0xA0,   // 0xA0 + 4
  BPMEM_TX_SETMODE1_4 = 0xA4,   // 0xA4 + 4
  BPMEM_TX_SETIMAGE0_4 = 0xA8,  // 0xA8 + 4
  BPMEM_TX_SETIMAGE1_4 = 0xAC,  // 0xA4 + 4
  BPMEM_TX_SETIMAGE2_4 = 0xB0,  // 0xB0 + 4
  BPMEM_TX_SETIMAGE3_4 = 0xB4,  // 0xB4 + 4
  BPMEM_TX_SETTLUT_4 = 0xB8,    // 0xB8 + 4
  BPMEM_TEV_COLOR_ENV = 0xC0,   // 0xC0 + (2 * 16)
  BPMEM_TEV_ALPHA_ENV = 0xC1,   // 0xC1 + (2 * 16)
  BPMEM_TEV_COLOR_RA = 0xE0,    // 0xE0 + (2 * 4)
  BPMEM_TEV_COLOR_BG = 0xE1,    // 0xE1 + (2 * 4)
  BPMEM_FOGRANGE = 0xE8,        // 0xE8 + 6
  BPMEM_FOGPARAM0 = 0xEE,
  BPMEM_FOGBMAGNITUDE = 0xEF,
  BPMEM_FOGBEXPONENT = 0xF0,
  BPMEM_FOGPARAM3 = 0xF1,
  BPMEM_FOGCOLOR = 0xF2,
  BPMEM_ALPHACOMPARE = 0xF3,
  BPMEM_BIAS = 0xF4,
  BPMEM_ZTEX2 = 0xF5,
  BPMEM_TEV_KSEL = 0xF6,  // 0xF6 + 8
  BPMEM_BP_MASK = 0xFE,
};

// Tev/combiner things

// TEV scaling type
enum class TevScale : u32
{
  Scale1 = 0,
  Scale2 = 1,
  Scale4 = 2,
  Divide2 = 3
};
template <>
struct fmt::formatter<TevScale> : EnumFormatter<TevScale::Divide2>
{
  formatter() : EnumFormatter({"1", "2", "4", "0.5"}) {}
};

// TEV combiner operator
enum class TevOp : u32
{
  Add = 0,
  Sub = 1,
};
template <>
struct fmt::formatter<TevOp> : EnumFormatter<TevOp::Sub>
{
  formatter() : EnumFormatter({"Add", "Subtract"}) {}
};

enum class TevCompareMode : u32
{
  R8 = 0,
  GR16 = 1,
  BGR24 = 2,
  RGB8 = 3,
  A8 = RGB8,
};
template <>
struct fmt::formatter<TevCompareMode> : EnumFormatter<TevCompareMode::RGB8>
{
  formatter() : EnumFormatter({"R8", "GR16", "BGR24", "RGB8 / A8"}) {}
};

enum class TevComparison : u32
{
  GT = 0,
  EQ = 1,
};
template <>
struct fmt::formatter<TevComparison> : EnumFormatter<TevComparison::EQ>
{
  formatter() : EnumFormatter({"Greater than", "Equal to"}) {}
};

// TEV color combiner input
enum class TevColorArg : u32
{
  PrevColor = 0,
  PrevAlpha = 1,
  Color0 = 2,
  Alpha0 = 3,
  Color1 = 4,
  Alpha1 = 5,
  Color2 = 6,
  Alpha2 = 7,
  TexColor = 8,
  TexAlpha = 9,
  RasColor = 10,
  RasAlpha = 11,
  One = 12,
  Half = 13,
  Konst = 14,
  Zero = 15
};
template <>
struct fmt::formatter<TevColorArg> : EnumFormatter<TevColorArg::Zero>
{
  static constexpr array_type names = {
      "prev.rgb", "prev.aaa", "c0.rgb",  "c0.aaa",  "c1.rgb", "c1.aaa", "c2.rgb",    "c2.aaa",
      "tex.rgb",  "tex.aaa",  "ras.rgb", "ras.aaa", "ONE",    "HALF",   "konst.rgb", "ZERO",
  };
  formatter() : EnumFormatter(names) {}
};

// TEV alpha combiner input
enum class TevAlphaArg : u32
{
  PrevAlpha = 0,
  Alpha0 = 1,
  Alpha1 = 2,
  Alpha2 = 3,
  TexAlpha = 4,
  RasAlpha = 5,
  Konst = 6,
  Zero = 7
};
template <>
struct fmt::formatter<TevAlphaArg> : EnumFormatter<TevAlphaArg::Zero>
{
  static constexpr array_type names = {
      "prev", "c0", "c1", "c2", "tex", "ras", "konst", "ZERO",
  };
  formatter() : EnumFormatter(names) {}
};

// TEV output registers
enum class TevOutput : u32
{
  Prev = 0,
  Color0 = 1,
  Color1 = 2,
  Color2 = 3,
};
template <>
struct fmt::formatter<TevOutput> : EnumFormatter<TevOutput::Color2>
{
  formatter() : EnumFormatter({"prev", "c0", "c1", "c2"}) {}
};

// Z-texture formats
enum class ZTexFormat : u32
{
  U8 = 0,
  U16 = 1,
  U24 = 2
};
template <>
struct fmt::formatter<ZTexFormat> : EnumFormatter<ZTexFormat::U24>
{
  formatter() : EnumFormatter({"u8", "u16", "u24"}) {}
};

// Z texture operator
enum class ZTexOp : u32
{
  Disabled = 0,
  Add = 1,
  Replace = 2
};
template <>
struct fmt::formatter<ZTexOp> : EnumFormatter<ZTexOp::Replace>
{
  formatter() : EnumFormatter({"Disabled", "Add", "Replace"}) {}
};

// TEV bias value
enum class TevBias : u32
{
  Zero = 0,
  AddHalf = 1,
  SubHalf = 2,
  Compare = 3
};
template <>
struct fmt::formatter<TevBias> : EnumFormatter<TevBias::Compare>
{
  formatter() : EnumFormatter({"0", "+0.5", "-0.5", "compare"}) {}
};

// Indirect texture format
enum class IndTexFormat : u32
{
  ITF_8 = 0,
  ITF_5 = 1,
  ITF_4 = 2,
  ITF_3 = 3
};
template <>
struct fmt::formatter<IndTexFormat> : EnumFormatter<IndTexFormat::ITF_3>
{
  formatter() : EnumFormatter({"ITF_8", "ITF_5", "ITF_4", "ITF_3"}) {}
};

// Indirect texture bias
enum class IndTexBias : u32
{
  None = 0,
  S = 1,
  T = 2,
  ST = 3,
  U = 4,
  SU = 5,
  TU_ = 6,  // conflicts with define in PowerPC.h
  STU = 7
};
template <>
struct fmt::formatter<IndTexBias> : EnumFormatter<IndTexBias::STU>
{
  formatter() : EnumFormatter({"None", "S", "T", "ST", "U", "SU", "TU", "STU"}) {}
};

enum class IndMtxIndex : u32
{
  Off = 0,
  Matrix0 = 1,
  Matrix1 = 2,
  Matrix2 = 3,
};
template <>
struct fmt::formatter<IndMtxIndex> : EnumFormatter<IndMtxIndex::Matrix2>
{
  formatter() : EnumFormatter({"Off", "Matrix 0", "Matrix 1", "Matrix 2"}) {}
};

enum class IndMtxId : u32
{
  Indirect = 0,
  S = 1,
  T = 2,
};
template <>
struct fmt::formatter<IndMtxId> : EnumFormatter<IndMtxId::T>
{
  formatter() : EnumFormatter({"Indirect", "S", "T"}) {}
};

// Indirect texture bump alpha
enum class IndTexBumpAlpha : u32
{
  Off = 0,
  S = 1,
  T = 2,
  U = 3
};
template <>
struct fmt::formatter<IndTexBumpAlpha> : EnumFormatter<IndTexBumpAlpha::U>
{
  formatter() : EnumFormatter({"Off", "S", "T", "U"}) {}
};

// Indirect texture wrap value
enum class IndTexWrap : u32
{
  ITW_OFF = 0,
  ITW_256 = 1,
  ITW_128 = 2,
  ITW_64 = 3,
  ITW_32 = 4,
  ITW_16 = 5,
  ITW_0 = 6
};
template <>
struct fmt::formatter<IndTexWrap> : EnumFormatter<IndTexWrap::ITW_0>
{
  formatter() : EnumFormatter({"Off", "256", "128", "64", "32", "16", "0"}) {}
};

union IND_MTXA
{
  BitField<0, 11, s32> ma;
  BitField<11, 11, s32> mb;
  BitField<22, 2, u8, u32> s0;  // bits 0-1 of scale factor
  u32 hex;
};
template <>
struct fmt::formatter<IND_MTXA>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const IND_MTXA& col, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Row 0 (ma): {} ({})\n"
                     "Row 1 (mb): {} ({})\n"
                     "Scale bits: {} (shifted: {})",
                     col.ma / 1024.0f, col.ma, col.mb / 1024.0f, col.mb, col.s0, col.s0);
  }
};

union IND_MTXB
{
  BitField<0, 11, s32> mc;
  BitField<11, 11, s32> md;
  BitField<22, 2, u8, u32> s1;  // bits 2-3 of scale factor
  u32 hex;
};
template <>
struct fmt::formatter<IND_MTXB>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const IND_MTXB& col, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Row 0 (mc): {} ({})\n"
                     "Row 1 (md): {} ({})\n"
                     "Scale bits: {} (shifted: {})",
                     col.mc / 1024.0f, col.mc, col.md / 1024.0f, col.md, col.s1, col.s1 << 2);
  }
};

union IND_MTXC
{
  BitField<0, 11, s32> me;
  BitField<11, 11, s32> mf;
  BitField<22, 1, u8, u32> s2;  // bit 4 of scale factor
  // The SDK treats the scale factor as 6 bits, 2 on each column; however, hardware seems to ignore
  // the top bit.
  BitField<22, 2, u8, u32> sdk_s2;
  u32 hex;
};
template <>
struct fmt::formatter<IND_MTXC>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const IND_MTXC& col, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Row 0 (me): {} ({})\n"
                     "Row 1 (mf): {} ({})\n"
                     "Scale bits: {} (shifted: {}), given to SDK as {} ({})",
                     col.me / 1024.0f, col.me, col.mf / 1024.0f, col.mf, col.s2, col.s2 << 4,
                     col.sdk_s2, col.sdk_s2 << 4);
  }
};

struct IND_MTX
{
  IND_MTXA col0;
  IND_MTXB col1;
  IND_MTXC col2;
  u8 GetScale() const { return (col0.s0 << 0) | (col1.s1 << 2) | (col2.s2 << 4); }
};

union IND_IMASK
{
  BitField<0, 24, u32> mask;
  u32 hex;
};

struct TevStageCombiner
{
  union ColorCombiner
  {
    // abc=8bit,d=10bit
    BitField<0, 4, TevColorArg> d;
    BitField<4, 4, TevColorArg> c;
    BitField<8, 4, TevColorArg> b;
    BitField<12, 4, TevColorArg> a;

    BitField<16, 2, TevBias> bias;
    BitField<18, 1, TevOp> op;                  // Applies when bias is not compare
    BitField<18, 1, TevComparison> comparison;  // Applies when bias is compare
    BitField<19, 1, bool, u32> clamp;

    BitField<20, 2, TevScale> scale;               // Applies when bias is not compare
    BitField<20, 2, TevCompareMode> compare_mode;  // Applies when bias is compare
    BitField<22, 2, TevOutput> dest;

    u32 hex;
  };
  union AlphaCombiner
  {
    BitField<0, 2, u32> rswap;
    BitField<2, 2, u32> tswap;
    BitField<4, 3, TevAlphaArg> d;
    BitField<7, 3, TevAlphaArg> c;
    BitField<10, 3, TevAlphaArg> b;
    BitField<13, 3, TevAlphaArg> a;

    BitField<16, 2, TevBias> bias;
    BitField<18, 1, TevOp> op;                  // Applies when bias is not compare
    BitField<18, 1, TevComparison> comparison;  // Applies when bias is compare
    BitField<19, 1, bool, u32> clamp;

    BitField<20, 2, TevScale> scale;               // Applies when bias is not compare
    BitField<20, 2, TevCompareMode> compare_mode;  // Applies when bias is compare
    BitField<22, 2, TevOutput> dest;

    u32 hex;
  };

  ColorCombiner colorC;
  AlphaCombiner alphaC;
};
template <>
struct fmt::formatter<TevStageCombiner::ColorCombiner>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TevStageCombiner::ColorCombiner& cc, FormatContext& ctx)
  {
    auto out = ctx.out();
    if (cc.bias != TevBias::Compare)
    {
      // Generate an equation view, simplifying out addition of zero and multiplication by 1
      // dest = (d (OP) ((1 - c)*a + c*b) + bias) * scale
      // or equivalently and more readably when the terms are not constants:
      // dest = (d (OP) lerp(a, b, c) + bias) * scale
      // Note that lerping is more complex than the first form shows; see PixelShaderGen's
      // WriteTevRegular for more details.

      static constexpr Common::EnumMap<const char*, TevColorArg::Zero> alt_names = {
          "prev.rgb", "prev.aaa", "c0.rgb",  "c0.aaa",  "c1.rgb", "c1.aaa", "c2.rgb",    "c2.aaa",
          "tex.rgb",  "tex.aaa",  "ras.rgb", "ras.aaa", "1",      ".5",     "konst.rgb", "0",
      };

      const bool has_d = cc.d != TevColorArg::Zero;
      // If c is one, (1 - c) is zero, so (1-c)*a is zero
      const bool has_ac = cc.a != TevColorArg::Zero && cc.c != TevColorArg::One;
      // If either b or c is zero, b*c is zero
      const bool has_bc = cc.b != TevColorArg::Zero && cc.c != TevColorArg::Zero;
      const bool has_bias = cc.bias != TevBias::Zero;  // != Compare is already known
      const bool has_scale = cc.scale != TevScale::Scale1;

      const char op = (cc.op == TevOp::Sub ? '-' : '+');

      if (cc.dest == TevOutput::Prev)
        out = format_to(out, "dest.rgb = ");
      else
        out = format_to(out, "{:n}.rgb = ", cc.dest);

      if (has_scale)
        out = format_to(out, "(");
      if (has_d)
        out = format_to(out, "{}", alt_names[cc.d]);
      if (has_ac || has_bc)
      {
        if (has_d)
          out = format_to(out, " {} ", op);
        else if (cc.op == TevOp::Sub)
          out = format_to(out, "{}", op);
        if (has_ac && has_bc)
        {
          if (cc.c == TevColorArg::Half)
          {
            // has_a and has_b imply that c is not Zero or One, and Half is the only remaining
            // numeric constant.  This results in an average.
            out = format_to(out, "({} + {})/2", alt_names[cc.a], alt_names[cc.b]);
          }
          else
          {
            out = format_to(out, "lerp({}, {}, {})", alt_names[cc.a], alt_names[cc.b],
                            alt_names[cc.c]);
          }
        }
        else if (has_ac)
        {
          if (cc.c == TevColorArg::Zero)
            out = format_to(out, "{}", alt_names[cc.a]);
          else if (cc.c == TevColorArg::Half)  // 1 - .5 is .5
            out = format_to(out, ".5*{}", alt_names[cc.a]);
          else
            out = format_to(out, "(1 - {})*{}", alt_names[cc.c], alt_names[cc.a]);
        }
        else  // has_bc
        {
          if (cc.c == TevColorArg::One)
            out = format_to(out, "{}", alt_names[cc.b]);
          else
            out = format_to(out, "{}*{}", alt_names[cc.c], alt_names[cc.b]);
        }
      }
      if (has_bias)
      {
        if (has_ac || has_bc || has_d)
          out = format_to(out, cc.bias == TevBias::AddHalf ? " + .5" : " - .5");
        else
          out = format_to(out, cc.bias == TevBias::AddHalf ? ".5" : "-.5");
      }
      else
      {
        // If nothing has been written so far, add a zero
        if (!(has_ac || has_bc || has_d))
          out = format_to(out, "0");
      }
      if (has_scale)
        out = format_to(out, ") * {:n}", cc.scale);
      out = format_to(out, "\n\n");
    }
    return format_to(ctx.out(),
                     "a: {}\n"
                     "b: {}\n"
                     "c: {}\n"
                     "d: {}\n"
                     "Bias: {}\n"
                     "Op: {} / Comparison: {}\n"
                     "Clamp: {}\n"
                     "Scale factor: {} / Compare mode: {}\n"
                     "Dest: {}",
                     cc.a, cc.b, cc.c, cc.d, cc.bias, cc.op, cc.comparison, cc.clamp ? "Yes" : "No",
                     cc.scale, cc.compare_mode, cc.dest);
  }
};
template <>
struct fmt::formatter<TevStageCombiner::AlphaCombiner>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TevStageCombiner::AlphaCombiner& ac, FormatContext& ctx)
  {
    auto out = ctx.out();
    if (ac.bias != TevBias::Compare)
    {
      // Generate an equation view, simplifying out addition of zero and multiplication by 1
      // dest = (d (OP) ((1 - c)*a + c*b) + bias) * scale
      // or equivalently and more readably when the terms are not constants:
      // dest = (d (OP) lerp(a, b, c) + bias) * scale
      // Note that lerping is more complex than the first form shows; see PixelShaderGen's
      // WriteTevRegular for more details.

      // We don't need an alt_names map here, unlike the color combiner, as the only special term is
      // Zero, and we we filter that out below.  However, we do need to append ".a" to all
      // parameters, to make it explicit that these are operations on the alpha term instead of the
      // 4-element vector.  We also need to use the :n specifier so that the numeric ID isn't shown.

      const bool has_d = ac.d != TevAlphaArg::Zero;
      // There is no c value for alpha that results in (1 - c) always being zero
      const bool has_ac = ac.a != TevAlphaArg::Zero;
      // If either b or c is zero, b*c is zero
      const bool has_bc = ac.b != TevAlphaArg::Zero && ac.c != TevAlphaArg::Zero;
      const bool has_bias = ac.bias != TevBias::Zero;  // != Compare is already known
      const bool has_scale = ac.scale != TevScale::Scale1;

      const char op = (ac.op == TevOp::Sub ? '-' : '+');

      if (ac.dest == TevOutput::Prev)
        out = format_to(out, "dest.a = ");
      else
        out = format_to(out, "{:n}.a = ", ac.dest);

      if (has_scale)
        out = format_to(out, "(");
      if (has_d)
        out = format_to(out, "{:n}.a", ac.d);
      if (has_ac || has_bc)
      {
        if (has_d)
          out = format_to(out, " {} ", op);
        else if (ac.op == TevOp::Sub)
          out = format_to(out, "{}", op);
        if (has_ac && has_bc)
        {
          out = format_to(out, "lerp({:n}.a, {:n}.a, {:n}.a)", ac.a, ac.b, ac.c);
        }
        else if (has_ac)
        {
          if (ac.c == TevAlphaArg::Zero)
            out = format_to(out, "{:n}.a", ac.a);
          else
            out = format_to(out, "(1 - {:n}.a)*{:n}.a", ac.c, ac.a);
        }
        else  // has_bc
        {
          out = format_to(out, "{:n}.a*{:n}.a", ac.c, ac.b);
        }
      }
      if (has_bias)
      {
        if (has_ac || has_bc || has_d)
          out = format_to(out, ac.bias == TevBias::AddHalf ? " + .5" : " - .5");
        else
          out = format_to(out, ac.bias == TevBias::AddHalf ? ".5" : "-.5");
      }
      else
      {
        // If nothing has been written so far, add a zero
        if (!(has_ac || has_bc || has_d))
          out = format_to(out, "0");
      }
      if (has_scale)
        out = format_to(out, ") * {:n}", ac.scale);
      out = format_to(out, "\n\n");
    }
    return format_to(out,
                     "a: {}\n"
                     "b: {}\n"
                     "c: {}\n"
                     "d: {}\n"
                     "Bias: {}\n"
                     "Op: {} / Comparison: {}\n"
                     "Clamp: {}\n"
                     "Scale factor: {} / Compare mode: {}\n"
                     "Dest: {}\n"
                     "Ras sel: {}\n"
                     "Tex sel: {}",
                     ac.a, ac.b, ac.c, ac.d, ac.bias, ac.op, ac.comparison, ac.clamp ? "Yes" : "No",
                     ac.scale, ac.compare_mode, ac.dest, ac.rswap, ac.tswap);
  }
};

// several discoveries:
// GXSetTevIndBumpST(tevstage, indstage, matrixind)
//  if ( matrix == 2 ) realmat = 6; // 10
//  else if ( matrix == 3 ) realmat = 7; // 11
//  else if ( matrix == 1 ) realmat = 5; // 9
//  GXSetTevIndirect(tevstage, indstage, 0, 3, realmat, 6, 6, 0, 0, 0)
//  GXSetTevIndirect(tevstage+1, indstage, 0, 3, realmat+4, 6, 6, 1, 0, 0)
//  GXSetTevIndirect(tevstage+2, indstage, 0, 0, 0, 0, 0, 1, 0, 0)

union TevStageIndirect
{
  BitField<0, 2, u32> bt;  // Indirect tex stage ID
  BitField<2, 2, IndTexFormat> fmt;
  BitField<4, 3, IndTexBias> bias;
  BitField<4, 1, bool, u32> bias_s;
  BitField<5, 1, bool, u32> bias_t;
  BitField<6, 1, bool, u32> bias_u;
  BitField<7, 2, IndTexBumpAlpha> bs;  // Indicates which coordinate will become the 'bump alpha'
  // Indicates which indirect matrix is used when matrix_id is Indirect.
  // Also always indicates which indirect matrix to use for the scale factor, even with S or T.
  BitField<9, 2, IndMtxIndex> matrix_index;
  // Should be set to Indirect (0) if matrix_index is Off (0)
  BitField<11, 2, IndMtxId> matrix_id;
  BitField<13, 3, IndTexWrap> sw;         // Wrapping factor for S of regular coord
  BitField<16, 3, IndTexWrap> tw;         // Wrapping factor for T of regular coord
  BitField<19, 1, bool, u32> lb_utclod;   // Use modified or unmodified texture
                                          // coordinates for LOD computation
  BitField<20, 1, bool, u32> fb_addprev;  // true if the texture coordinate results from the
                                          // previous TEV stage should be added

  struct
  {
    u32 hex : 21;
    u32 unused : 11;
  };

  u32 fullhex;

  // If bs and matrix are zero, the result of the stage is independent of
  // the texture sample data, so we can skip sampling the texture.
  bool IsActive() const { return bs != IndTexBumpAlpha::Off || matrix_index != IndMtxIndex::Off; }
};
template <>
struct fmt::formatter<TevStageIndirect>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TevStageIndirect& tevind, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Indirect tex stage ID: {}\n"
                     "Format: {}\n"
                     "Bias: {}\n"
                     "Bump alpha: {}\n"
                     "Offset matrix index: {}\n"
                     "Offset matrix ID: {}\n"
                     "Regular coord S wrapping factor: {}\n"
                     "Regular coord T wrapping factor: {}\n"
                     "Use modified texture coordinates for LOD computation: {}\n"
                     "Add texture coordinates from previous TEV stage: {}",
                     tevind.bt, tevind.fmt, tevind.bias, tevind.bs, tevind.matrix_index,
                     tevind.matrix_id, tevind.sw, tevind.tw, tevind.lb_utclod ? "Yes" : "No",
                     tevind.fb_addprev ? "Yes" : "No");
  }
};

enum class RasColorChan : u32
{
  Color0 = 0,
  Color1 = 1,
  AlphaBump = 5,
  NormalizedAlphaBump = 6,
  Zero = 7,
};
template <>
struct fmt::formatter<RasColorChan> : EnumFormatter<RasColorChan::Zero>
{
  static constexpr array_type names = {
      "Color chan 0", "Color chan 1", nullptr,           nullptr,
      nullptr,        "Alpha bump",   "Norm alpha bump", "Zero",
  };
  formatter() : EnumFormatter(names) {}
};

union TwoTevStageOrders
{
  BitField<0, 3, u32> texmap0;  // Indirect tex stage texmap
  BitField<3, 3, u32> texcoord0;
  BitField<6, 1, bool, u32> enable0;  // true if should read from texture
  BitField<7, 3, RasColorChan> colorchan0;

  BitField<12, 3, u32> texmap1;
  BitField<15, 3, u32> texcoord1;
  BitField<18, 1, bool, u32> enable1;  // true if should read from texture
  BitField<19, 3, RasColorChan> colorchan1;

  u32 hex;
  u32 getTexMap(int i) const { return i ? texmap1.Value() : texmap0.Value(); }
  u32 getTexCoord(int i) const { return i ? texcoord1.Value() : texcoord0.Value(); }
  u32 getEnable(int i) const { return i ? enable1.Value() : enable0.Value(); }
  RasColorChan getColorChan(int i) const { return i ? colorchan1.Value() : colorchan0.Value(); }
};
template <>
struct fmt::formatter<TwoTevStageOrders>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TwoTevStageOrders& stages, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Stage 0 texmap: {}\nStage 0 tex coord: {}\n"
                     "Stage 0 enable texmap: {}\nStage 0 color channel: {}\n"
                     "Stage 1 texmap: {}\nStage 1 tex coord: {}\n"
                     "Stage 1 enable texmap: {}\nStage 1 color channel: {}\n",
                     stages.texmap0, stages.texcoord0, stages.enable0 ? "Yes" : "No",
                     stages.colorchan0, stages.texmap1, stages.texcoord1,
                     stages.enable1 ? "Yes" : "No", stages.colorchan1);
  }
};

union TEXSCALE
{
  BitField<0, 4, u32> ss0;   // Indirect tex stage 0, 2^(-ss0)
  BitField<4, 4, u32> ts0;   // Indirect tex stage 0
  BitField<8, 4, u32> ss1;   // Indirect tex stage 1
  BitField<12, 4, u32> ts1;  // Indirect tex stage 1
  u32 hex;
};
template <>
struct fmt::formatter<TEXSCALE>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TEXSCALE& scale, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Even stage S scale: {} ({})\n"
                     "Even stage T scale: {} ({})\n"
                     "Odd stage S scale: {} ({})\n"
                     "Odd stage T scale: {} ({})",
                     scale.ss0, 1.f / (1 << scale.ss0), scale.ts0, 1.f / (1 << scale.ts0),
                     scale.ss1, 1.f / (1 << scale.ss1), scale.ts1, 1.f / (1 << scale.ts1));
  }
};

union RAS1_IREF
{
  BitField<0, 3, u32> bi0;  // Indirect tex stage 0 ntexmap
  BitField<3, 3, u32> bc0;  // Indirect tex stage 0 ntexcoord
  BitField<6, 3, u32> bi1;
  BitField<9, 3, u32> bc1;
  BitField<12, 3, u32> bi2;
  BitField<15, 3, u32> bc2;
  BitField<18, 3, u32> bi3;
  BitField<21, 3, u32> bc3;
  u32 hex;

  u32 getTexCoord(int i) const { return (hex >> (6 * i + 3)) & 7; }
  u32 getTexMap(int i) const { return (hex >> (6 * i)) & 7; }
};
template <>
struct fmt::formatter<RAS1_IREF>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const RAS1_IREF& indref, FormatContext& ctx)
  {
    // The field names here are suspicious, since there is no bi3 or bc2
    return format_to(ctx.out(),
                     "Stage 0 ntexmap: {}\nStage 0 ntexcoord: {}\n"
                     "Stage 1 ntexmap: {}\nStage 1 ntexcoord: {}\n"
                     "Stage 2 ntexmap: {}\nStage 2 ntexcoord: {}\n"
                     "Stage 3 ntexmap: {}\nStage 3 ntexcoord: {}",
                     indref.bi0, indref.bc0, indref.bi1, indref.bc1, indref.bi2, indref.bc2,
                     indref.bi3, indref.bc3);
  }
};

// Texture structs
enum class WrapMode : u32
{
  Clamp = 0,
  Repeat = 1,
  Mirror = 2,
  // Hardware testing indicates that WrapMode set to 3 behaves the same as clamp, though this is an
  // invalid value
};
template <>
struct fmt::formatter<WrapMode> : EnumFormatter<WrapMode::Mirror>
{
  formatter() : EnumFormatter({"Clamp", "Repeat", "Mirror"}) {}
};

enum class MipMode : u32
{
  None = 0,
  Point = 1,
  Linear = 2,
};
template <>
struct fmt::formatter<MipMode> : EnumFormatter<MipMode::Linear>
{
  formatter() : EnumFormatter({"None", "Mip point", "Mip linear"}) {}
};

enum class FilterMode : u32
{
  Near = 0,
  Linear = 1,
};
template <>
struct fmt::formatter<FilterMode> : EnumFormatter<FilterMode::Linear>
{
  formatter() : EnumFormatter({"Near", "Linear"}) {}
};

enum class LODType : u32
{
  Edge = 0,
  Diagonal = 1,
};
template <>
struct fmt::formatter<LODType> : EnumFormatter<LODType::Diagonal>
{
  formatter() : EnumFormatter({"Edge LOD", "Diagonal LOD"}) {}
};

enum class MaxAniso
{
  One = 0,
  Two = 1,
  Four = 2,
};
template <>
struct fmt::formatter<MaxAniso> : EnumFormatter<MaxAniso::Four>
{
  formatter() : EnumFormatter({"1", "2", "4"}) {}
};

union TexMode0
{
  BitField<0, 2, WrapMode> wrap_s;
  BitField<2, 2, WrapMode> wrap_t;
  BitField<4, 1, FilterMode> mag_filter;
  BitField<5, 2, MipMode> mipmap_filter;
  BitField<7, 1, FilterMode> min_filter;
  BitField<8, 1, LODType> diag_lod;
  BitField<9, 8, s32> lod_bias;
  BitField<19, 2, MaxAniso> max_aniso;
  BitField<21, 1, bool, u32> lod_clamp;
  u32 hex;
};
template <>
struct fmt::formatter<TexMode0>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TexMode0& mode, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Wrap S: {}\n"
                     "Wrap T: {}\n"
                     "Mag filter: {}\n"
                     "Mipmap filter: {}\n"
                     "Min filter: {}\n"
                     "LOD type: {}\n"
                     "LOD bias: {} ({})\n"
                     "Max anisotropic filtering: {}\n"
                     "LOD/bias clamp: {}",
                     mode.wrap_s, mode.wrap_t, mode.mag_filter, mode.mipmap_filter, mode.min_filter,
                     mode.diag_lod, mode.lod_bias, mode.lod_bias / 32.f, mode.max_aniso,
                     mode.lod_clamp ? "Yes" : "No");
  }
};

union TexMode1
{
  BitField<0, 8, u32> min_lod;
  BitField<8, 8, u32> max_lod;
  u32 hex;
};
template <>
struct fmt::formatter<TexMode1>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TexMode1& mode, FormatContext& ctx)
  {
    return format_to(ctx.out(), "Min LOD: {} ({})\nMax LOD: {} ({})", mode.min_lod,
                     mode.min_lod / 16.f, mode.max_lod, mode.max_lod / 16.f);
  }
};

union TexImage0
{
  BitField<0, 10, u32> width;    // Actually w-1
  BitField<10, 10, u32> height;  // Actually h-1
  BitField<20, 4, TextureFormat> format;
  u32 hex;
};
template <>
struct fmt::formatter<TexImage0>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TexImage0& teximg, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Width: {}\n"
                     "Height: {}\n"
                     "Format: {}",
                     teximg.width + 1, teximg.height + 1, teximg.format);
  }
};

union TexImage1
{
  BitField<0, 15, u32> tmem_even;  // TMEM line index for even LODs
  BitField<15, 3, u32> cache_width;
  BitField<18, 3, u32> cache_height;
  // true if this texture is managed manually (false means we'll
  // autofetch the texture data whenever it changes)
  BitField<21, 1, bool, u32> cache_manually_managed;
  u32 hex;
};
template <>
struct fmt::formatter<TexImage1>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TexImage1& teximg, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Even TMEM Offset: {:x}\n"
                     "Even TMEM Width: {}\n"
                     "Even TMEM Height: {}\n"
                     "Cache is manually managed: {}",
                     teximg.tmem_even, teximg.cache_width, teximg.cache_height,
                     teximg.cache_manually_managed ? "Yes" : "No");
  }
};

union TexImage2
{
  BitField<0, 15, u32> tmem_odd;  // tmem line index for odd LODs
  BitField<15, 3, u32> cache_width;
  BitField<18, 3, u32> cache_height;
  u32 hex;
};
template <>
struct fmt::formatter<TexImage2>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TexImage2& teximg, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Odd TMEM Offset: {:x}\n"
                     "Odd TMEM Width: {}\n"
                     "Odd TMEM Height: {}",
                     teximg.tmem_odd, teximg.cache_width, teximg.cache_height);
  }
};

union TexImage3
{
  BitField<0, 24, u32> image_base;  // address in memory >> 5 (was 20 for GC)
  u32 hex;
};
template <>
struct fmt::formatter<TexImage3>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TexImage3& teximg, FormatContext& ctx)
  {
    return format_to(ctx.out(), "Source address (32 byte aligned): 0x{:06X}",
                     teximg.image_base << 5);
  }
};

union TexTLUT
{
  BitField<0, 10, u32> tmem_offset;
  BitField<10, 2, TLUTFormat> tlut_format;
  u32 hex;
};
template <>
struct fmt::formatter<TexTLUT>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TexTLUT& tlut, FormatContext& ctx)
  {
    return format_to(ctx.out(), "Address: {:08x}\nFormat: {}", tlut.tmem_offset << 9,
                     tlut.tlut_format);
  }
};

union ZTex1
{
  BitField<0, 24, u32> bias;
  u32 hex;
};

union ZTex2
{
  BitField<0, 2, ZTexFormat> type;
  BitField<2, 2, ZTexOp> op;
  u32 hex;
};
template <>
struct fmt::formatter<ZTex2>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const ZTex2& ztex2, FormatContext& ctx)
  {
    return format_to(ctx.out(), "Type: {}\nOperation: {}", ztex2.type, ztex2.op);
  }
};

// Geometry/other structs
enum class CullMode : u32
{
  None = 0,
  Back = 1,   // cull back-facing primitives
  Front = 2,  // cull front-facing primitives
  All = 3,    // cull all primitives
};
template <>
struct fmt::formatter<CullMode> : EnumFormatter<CullMode::All>
{
  static constexpr array_type names = {
      "None",
      "Back-facing primitives only",
      "Front-facing primitives only",
      "All primitives",
  };
  formatter() : EnumFormatter(names) {}
};

union GenMode
{
  BitField<0, 4, u32> numtexgens;
  BitField<4, 3, u32> numcolchans;
  BitField<7, 1, u32> unused;              // 1 bit unused?
  BitField<8, 1, bool, u32> flat_shading;  // unconfirmed
  BitField<9, 1, bool, u32> multisampling;
  // This value is 1 less than the actual number (0-15 map to 1-16).
  // In other words there is always at least 1 tev stage
  BitField<10, 4, u32> numtevstages;
  BitField<14, 2, CullMode> cullmode;
  BitField<16, 3, u32> numindstages;
  BitField<19, 1, bool, u32> zfreeze;

  u32 hex;
};
template <>
struct fmt::formatter<GenMode>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const GenMode& mode, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Num tex gens: {}\n"
                     "Num color channels: {}\n"
                     "Unused bit: {}\n"
                     "Flat shading (unconfirmed): {}\n"
                     "Multisampling: {}\n"
                     "Num TEV stages: {}\n"
                     "Cull mode: {}\n"
                     "Num indirect stages: {}\n"
                     "ZFreeze: {}",
                     mode.numtexgens, mode.numcolchans, mode.unused,
                     mode.flat_shading ? "Yes" : "No", mode.multisampling ? "Yes" : "No",
                     mode.numtevstages + 1, mode.cullmode, mode.numindstages,
                     mode.zfreeze ? "Yes" : "No");
  }
};

enum class AspectRatioAdjustment
{
  DontAdjust = 0,
  Adjust = 1,
};
template <>
struct fmt::formatter<AspectRatioAdjustment> : EnumFormatter<AspectRatioAdjustment::Adjust>
{
  formatter() : EnumFormatter({"Don't adjust", "Adjust"}) {}
};

union LPSize
{
  BitField<0, 8, u32> linesize;   // in 1/6th pixels
  BitField<8, 8, u32> pointsize;  // in 1/6th pixels
  BitField<16, 3, u32> lineoff;
  BitField<19, 3, u32> pointoff;
  // interlacing: adjust for pixels having AR of 1/2
  BitField<22, 1, AspectRatioAdjustment> adjust_for_aspect_ratio;
  u32 hex;
};
template <>
struct fmt::formatter<LPSize>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const LPSize& lp, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Line size: {} ({:.3} pixels)\n"
                     "Point size: {} ({:.3} pixels)\n"
                     "Line offset: {}\n"
                     "Point offset: {}\n"
                     "Adjust line aspect ratio: {}",
                     lp.linesize, lp.linesize / 6.f, lp.pointsize, lp.pointsize / 6.f, lp.lineoff,
                     lp.pointoff, lp.adjust_for_aspect_ratio);
  }
};

union X12Y12
{
  BitField<0, 12, u32> y;
  BitField<12, 12, u32> x;
  u32 hex;
};
union X10Y10
{
  BitField<0, 10, u32> x;
  BitField<10, 10, u32> y;
  u32 hex;
};
union S32X10Y10
{
  BitField<0, 10, s32> x;
  BitField<10, 10, s32> y;
  u32 hex;
};

// Framebuffer/pixel stuff (incl fog)
enum class SrcBlendFactor : u32
{
  Zero = 0,
  One = 1,
  DstClr = 2,
  InvDstClr = 3,
  SrcAlpha = 4,
  InvSrcAlpha = 5,
  DstAlpha = 6,
  InvDstAlpha = 7
};
template <>
struct fmt::formatter<SrcBlendFactor> : EnumFormatter<SrcBlendFactor::InvDstAlpha>
{
  static constexpr array_type names = {"0",         "1",           "dst_color", "1-dst_color",
                                       "src_alpha", "1-src_alpha", "dst_alpha", "1-dst_alpha"};
  formatter() : EnumFormatter(names) {}
};

enum class DstBlendFactor : u32
{
  Zero = 0,
  One = 1,
  SrcClr = 2,
  InvSrcClr = 3,
  SrcAlpha = 4,
  InvSrcAlpha = 5,
  DstAlpha = 6,
  InvDstAlpha = 7
};
template <>
struct fmt::formatter<DstBlendFactor> : EnumFormatter<DstBlendFactor::InvDstAlpha>
{
  static constexpr array_type names = {"0",         "1",           "src_color", "1-src_color",
                                       "src_alpha", "1-src_alpha", "dst_alpha", "1-dst_alpha"};
  formatter() : EnumFormatter(names) {}
};

enum class LogicOp : u32
{
  Clear = 0,
  And = 1,
  AndReverse = 2,
  Copy = 3,
  AndInverted = 4,
  NoOp = 5,
  Xor = 6,
  Or = 7,
  Nor = 8,
  Equiv = 9,
  Invert = 10,
  OrReverse = 11,
  CopyInverted = 12,
  OrInverted = 13,
  Nand = 14,
  Set = 15
};
template <>
struct fmt::formatter<LogicOp> : EnumFormatter<LogicOp::Set>
{
  static constexpr array_type names = {
      "Clear (0)",
      "And (src & dst)",
      "And Reverse (src & ~dst)",
      "Copy (src)",
      "And Inverted (~src & dst)",
      "NoOp (dst)",
      "Xor (src ^ dst)",
      "Or (src | dst)",
      "Nor (~(src | dst))",
      "Equiv (~(src ^ dst))",
      "Invert (~dst)",
      "Or Reverse (src | ~dst)",
      "Copy Inverted (~src)",
      "Or Inverted (~src | dst)",
      "Nand (~(src & dst))",
      "Set (1)",
  };
  formatter() : EnumFormatter(names) {}
};

union BlendMode
{
  BitField<0, 1, bool, u32> blendenable;
  BitField<1, 1, bool, u32> logicopenable;
  BitField<2, 1, bool, u32> dither;
  BitField<3, 1, bool, u32> colorupdate;
  BitField<4, 1, bool, u32> alphaupdate;
  BitField<5, 3, DstBlendFactor> dstfactor;
  BitField<8, 3, SrcBlendFactor> srcfactor;
  BitField<11, 1, bool, u32> subtract;
  BitField<12, 4, LogicOp> logicmode;

  u32 hex;

  bool UseLogicOp() const;
};
template <>
struct fmt::formatter<BlendMode>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const BlendMode& mode, FormatContext& ctx)
  {
    static constexpr std::array<const char*, 2> no_yes = {"No", "Yes"};
    return format_to(ctx.out(),
                     "Enable: {}\n"
                     "Logic ops: {}\n"
                     "Dither: {}\n"
                     "Color write: {}\n"
                     "Alpha write: {}\n"
                     "Dest factor: {}\n"
                     "Source factor: {}\n"
                     "Subtract: {}\n"
                     "Logic mode: {}",
                     no_yes[mode.blendenable], no_yes[mode.logicopenable], no_yes[mode.dither],
                     no_yes[mode.colorupdate], no_yes[mode.alphaupdate], mode.dstfactor,
                     mode.srcfactor, no_yes[mode.subtract], mode.logicmode);
  }
};

union FogParam0
{
  BitField<0, 11, u32> mant;
  BitField<11, 8, u32> exp;
  BitField<19, 1, u32> sign;

  u32 hex;
  float FloatValue() const;
};
template <>
struct fmt::formatter<FogParam0>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const FogParam0& param, FormatContext& ctx)
  {
    return format_to(ctx.out(), "A value: {}\nMantissa: {}\nExponent: {}\nSign: {}",
                     param.FloatValue(), param.mant, param.exp, param.sign ? '-' : '+');
  }
};

enum class FogProjection : u32
{
  Perspective = 0,
  Orthographic = 1,
};
template <>
struct fmt::formatter<FogProjection> : EnumFormatter<FogProjection::Orthographic>
{
  formatter() : EnumFormatter({"Perspective", "Orthographic"}) {}
};

enum class FogType : u32
{
  Off = 0,
  Linear = 2,
  Exp = 4,
  ExpSq = 5,
  BackwardsExp = 6,
  BackwardsExpSq = 7,
};
template <>
struct fmt::formatter<FogType> : EnumFormatter<FogType::BackwardsExpSq>
{
  static constexpr array_type names = {
      "Off (no fog)",
      nullptr,
      "Linear fog",
      nullptr,
      "Exponential fog",
      "Exponential-squared fog",
      "Backwards exponential fog",
      "Backwards exponenential-sequared fog",
  };
  formatter() : EnumFormatter(names) {}
};

union FogParam3
{
  BitField<0, 11, u32> c_mant;
  BitField<11, 8, u32> c_exp;
  BitField<19, 1, u32> c_sign;
  BitField<20, 1, FogProjection> proj;
  BitField<21, 3, FogType> fsel;

  u32 hex;
  float FloatValue() const;
};
template <>
struct fmt::formatter<FogParam3>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const FogParam3& param, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "C value: {}\nMantissa: {}\nExponent: {}\nSign: {}\nProjection: {}\nFsel: {}",
                     param.FloatValue(), param.c_mant, param.c_exp, param.c_sign ? '-' : '+',
                     param.proj, param.fsel);
  }
};

union FogRangeKElement
{
  BitField<0, 12, u32> HI;
  BitField<12, 12, u32> LO;

  // TODO: Which scaling coefficient should we use here? This is just a guess!
  float GetValue(int i) const { return (i ? HI.Value() : LO.Value()) / 256.f; }
  u32 HEX;
};

struct FogRangeParams
{
  union RangeBase
  {
    BitField<0, 10, u32> Center;  // viewport center + 342
    BitField<10, 1, bool, u32> Enabled;
    u32 hex;
  };
  RangeBase Base;
  FogRangeKElement K[5];
};
template <>
struct fmt::formatter<FogRangeParams::RangeBase>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const FogRangeParams::RangeBase& range, FormatContext& ctx)
  {
    return format_to(ctx.out(), "Center: {}\nEnabled: {}", range.Center,
                     range.Enabled ? "Yes" : "No");
  }
};
template <>
struct fmt::formatter<FogRangeKElement>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const FogRangeKElement& range, FormatContext& ctx)
  {
    return format_to(ctx.out(), "High: {}\nLow: {}", range.HI, range.LO);
  }
};

// final eq: ze = A/(B_MAG - (Zs>>B_SHF));
struct FogParams
{
  FogParam0 a;
  u32 b_magnitude;
  u32 b_shift;  // b's exp + 1?
  FogParam3 c_proj_fsel;

  union FogColor
  {
    BitField<0, 8, u32> b;
    BitField<8, 8, u32> g;
    BitField<16, 8, u32> r;
    u32 hex;
  };

  FogColor color;  // 0:b 8:g 16:r - nice!

  // Special case where a and c are infinite and the sign matches, resulting in a result of NaN.
  bool IsNaNCase() const;
  float GetA() const;

  // amount to subtract from eyespacez after range adjustment
  float GetC() const;
};
template <>
struct fmt::formatter<FogParams::FogColor>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const FogParams::FogColor& color, FormatContext& ctx)
  {
    return format_to(ctx.out(), "Red: {}\nGreen: {}\nBlue: {}", color.r, color.g, color.b);
  }
};

enum class CompareMode : u32
{
  Never = 0,
  Less = 1,
  Equal = 2,
  LEqual = 3,
  Greater = 4,
  NEqual = 5,
  GEqual = 6,
  Always = 7
};
template <>
struct fmt::formatter<CompareMode> : EnumFormatter<CompareMode::Always>
{
  static constexpr array_type names = {"Never",   "Less",   "Equal",  "LEqual",
                                       "Greater", "NEqual", "GEqual", "Always"};
  formatter() : EnumFormatter(names) {}
};

union ZMode
{
  BitField<0, 1, bool, u32> testenable;
  BitField<1, 3, CompareMode> func;
  BitField<4, 1, bool, u32> updateenable;

  u32 hex;
};
template <>
struct fmt::formatter<ZMode>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const ZMode& mode, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Enable test: {}\n"
                     "Compare function: {}\n"
                     "Enable updates: {}",
                     mode.testenable ? "Yes" : "No", mode.func, mode.updateenable ? "Yes" : "No");
  }
};

union ConstantAlpha
{
  BitField<0, 8, u32> alpha;
  BitField<8, 1, bool, u32> enable;
  u32 hex;
};
template <>
struct fmt::formatter<ConstantAlpha>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const ConstantAlpha& c, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Enable: {}\n"
                     "Alpha value: {:02x}",
                     c.enable ? "Yes" : "No", c.alpha);
  }
};

union FieldMode
{
  // adjust vertex tex LOD computation to account for interlacing
  BitField<0, 1, AspectRatioAdjustment> texLOD;
  u32 hex;
};
template <>
struct fmt::formatter<FieldMode>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const FieldMode& mode, FormatContext& ctx)
  {
    return format_to(ctx.out(), "Adjust vertex tex LOD computation to account for interlacing: {}",
                     mode.texLOD);
  }
};

enum class FieldMaskState : u32
{
  Skip = 0,
  Write = 1,
};
template <>
struct fmt::formatter<FieldMaskState> : EnumFormatter<FieldMaskState::Write>
{
  formatter() : EnumFormatter({"Skipped", "Written"}) {}
};

union FieldMask
{
  // Fields are written to the EFB only if their bit is set to write.
  BitField<0, 1, FieldMaskState> odd;
  BitField<1, 1, FieldMaskState> even;
  u32 hex;
};
template <>
struct fmt::formatter<FieldMask>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const FieldMask& mask, FormatContext& ctx)
  {
    return format_to(ctx.out(), "Odd field: {}\nEven field: {}", mask.odd, mask.even);
  }
};

enum class PixelFormat : u32
{
  RGB8_Z24 = 0,
  RGBA6_Z24 = 1,
  RGB565_Z16 = 2,
  Z24 = 3,
  Y8 = 4,
  U8 = 5,
  V8 = 6,
  YUV420 = 7,
  INVALID_FMT = 0xffffffff,  // Used by Dolphin to represent a missing value.
};
template <>
struct fmt::formatter<PixelFormat> : EnumFormatter<PixelFormat::YUV420>
{
  static constexpr array_type names = {"RGB8_Z24", "RGBA6_Z24", "RGB565_Z16", "Z24",
                                       "Y8",       "U8",        "V8",         "YUV420"};
  formatter() : EnumFormatter(names) {}
};

enum class DepthFormat : u32
{
  ZLINEAR = 0,
  ZNEAR = 1,
  ZMID = 2,
  ZFAR = 3,

  // It seems these Z formats aren't supported/were removed ?
  ZINV_LINEAR = 4,
  ZINV_NEAR = 5,
  ZINV_MID = 6,
  ZINV_FAR = 7
};
template <>
struct fmt::formatter<DepthFormat> : EnumFormatter<DepthFormat::ZINV_FAR>
{
  static constexpr array_type names = {
      "linear",     "compressed (near)",     "compressed (mid)",     "compressed (far)",
      "inv linear", "compressed (inv near)", "compressed (inv mid)", "compressed (inv far)",
  };
  formatter() : EnumFormatter(names) {}
};

union PEControl
{
  BitField<0, 3, PixelFormat> pixel_format;
  BitField<3, 3, DepthFormat> zformat;
  BitField<6, 1, bool, u32> early_ztest;

  u32 hex;
};
template <>
struct fmt::formatter<PEControl>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const PEControl& config, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "EFB pixel format: {}\n"
                     "Depth format: {}\n"
                     "Early depth test: {}",
                     config.pixel_format, config.zformat, config.early_ztest ? "Yes" : "No");
  }
};

// Texture coordinate stuff

union TCInfo
{
  BitField<0, 16, u32> scale_minus_1;
  BitField<16, 1, bool, u32> range_bias;
  BitField<17, 1, bool, u32> cylindric_wrap;
  // These bits only have effect in the s field of TCoordInfo
  BitField<18, 1, bool, u32> line_offset;
  BitField<19, 1, bool, u32> point_offset;
  u32 hex;
};
template <>
struct fmt::formatter<TCInfo>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TCInfo& info, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Scale: {}\n"
                     "Range bias: {}\n"
                     "Cylindric wrap: {}\n"
                     "Use line offset: {} (s only)\n"
                     "Use point offset: {} (s only)",
                     info.scale_minus_1 + 1, info.range_bias ? "Yes" : "No",
                     info.cylindric_wrap ? "Yes" : "No", info.line_offset ? "Yes" : "No",
                     info.point_offset ? "Yes" : "No");
  }
};

struct TCoordInfo
{
  TCInfo s;
  TCInfo t;
};

enum class TevRegType : u32
{
  Color = 0,
  Constant = 1,
};
template <>
struct fmt::formatter<TevRegType> : EnumFormatter<TevRegType::Constant>
{
  formatter() : EnumFormatter({"Color", "Constant"}) {}
};

struct TevReg
{
  // TODO: Check if Konst uses all 11 bits or just 8
  union RA
  {
    u32 hex;

    BitField<0, 11, s32> red;
    BitField<12, 11, s32> alpha;
    BitField<23, 1, TevRegType, u32> type;
  };
  union BG
  {
    u32 hex;

    BitField<0, 11, s32> blue;
    BitField<12, 11, s32> green;
    BitField<23, 1, TevRegType, u32> type;
  };

  RA ra;
  BG bg;
};
template <>
struct fmt::formatter<TevReg::RA>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TevReg::RA& ra, FormatContext& ctx)
  {
    return format_to(ctx.out(), "Type: {}\nAlpha: {:03x}\nRed: {:03x}", ra.type, ra.alpha, ra.red);
  }
};
template <>
struct fmt::formatter<TevReg::BG>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TevReg::BG& bg, FormatContext& ctx)
  {
    return format_to(ctx.out(), "Type: {}\nGreen: {:03x}\nBlue: {:03x}", bg.type, bg.green,
                     bg.blue);
  }
};
template <>
struct fmt::formatter<TevReg>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TevReg& reg, FormatContext& ctx)
  {
    return format_to(ctx.out(), "{}\n{}", reg.ra, reg.bg);
  }
};

enum class KonstSel : u32
{
  V1 = 0,
  V7_8 = 1,
  V3_4 = 2,
  V5_8 = 3,
  V1_2 = 4,
  V3_8 = 5,
  V1_4 = 6,
  V1_8 = 7,
  // 8-11 are invalid values that output 0 (8-15 for alpha)
  K0 = 12,  // Color only
  K1 = 13,  // Color only
  K2 = 14,  // Color only
  K3 = 15,  // Color only
  K0_R = 16,
  K1_R = 17,
  K2_R = 18,
  K3_R = 19,
  K0_G = 20,
  K1_G = 21,
  K2_G = 22,
  K3_G = 23,
  K0_B = 24,
  K1_B = 25,
  K2_B = 26,
  K3_B = 27,
  K0_A = 28,
  K1_A = 29,
  K2_A = 30,
  K3_A = 31,
};
template <>
struct fmt::formatter<KonstSel> : EnumFormatter<KonstSel::K3_A>
{
  static constexpr array_type names = {
      "1",
      "7/8",
      "3/4",
      "5/8",
      "1/2",
      "3/8",
      "1/4",
      "1/8",
      nullptr,
      nullptr,
      nullptr,
      nullptr,
      "Konst 0 RGB (invalid for alpha)",
      "Konst 1 RGB (invalid for alpha)",
      "Konst 2 RGB (invalid for alpha)",
      "Konst 3 RGB (invalid for alpha)",
      "Konst 0 Red",
      "Konst 1 Red",
      "Konst 2 Red",
      "Konst 3 Red",
      "Konst 0 Green",
      "Konst 1 Green",
      "Konst 2 Green",
      "Konst 3 Green",
      "Konst 0 Blue",
      "Konst 1 Blue",
      "Konst 2 Blue",
      "Konst 3 Blue",
      "Konst 0 Alpha",
      "Konst 1 Alpha",
      "Konst 2 Alpha",
      "Konst 3 Alpha",
  };
  formatter() : EnumFormatter(names) {}
};

union TevKSel
{
  BitField<0, 2, u32> swap1;
  BitField<2, 2, u32> swap2;
  BitField<4, 5, KonstSel> kcsel0;
  BitField<9, 5, KonstSel> kasel0;
  BitField<14, 5, KonstSel> kcsel1;
  BitField<19, 5, KonstSel> kasel1;
  u32 hex;

  KonstSel getKC(int i) const { return i ? kcsel1.Value() : kcsel0.Value(); }
  KonstSel getKA(int i) const { return i ? kasel1.Value() : kasel0.Value(); }
};
template <>
struct fmt::formatter<TevKSel>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const TevKSel& ksel, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Swap 1: {}\nSwap 2: {}\nColor sel 0: {}\nAlpha sel 0: {}\n"
                     "Color sel 1: {}\nAlpha sel 1: {}",
                     ksel.swap1, ksel.swap2, ksel.kcsel0, ksel.kasel0, ksel.kcsel1, ksel.kasel1);
  }
};

enum class AlphaTestOp : u32
{
  And = 0,
  Or = 1,
  Xor = 2,
  Xnor = 3
};
template <>
struct fmt::formatter<AlphaTestOp> : EnumFormatter<AlphaTestOp::Xnor>
{
  formatter() : EnumFormatter({"And", "Or", "Xor", "Xnor"}) {}
};

enum class AlphaTestResult
{
  Undetermined = 0,
  Fail = 1,
  Pass = 2,
};

union AlphaTest
{
  BitField<0, 8, u32> ref0;
  BitField<8, 8, u32> ref1;
  BitField<16, 3, CompareMode> comp0;
  BitField<19, 3, CompareMode> comp1;
  BitField<22, 2, AlphaTestOp> logic;

  u32 hex;

  DOLPHIN_FORCE_INLINE AlphaTestResult TestResult() const
  {
    switch (logic)
    {
    case AlphaTestOp::And:
      if (comp0 == CompareMode::Always && comp1 == CompareMode::Always)
        return AlphaTestResult::Pass;
      if (comp0 == CompareMode::Never || comp1 == CompareMode::Never)
        return AlphaTestResult::Fail;
      break;

    case AlphaTestOp::Or:
      if (comp0 == CompareMode::Always || comp1 == CompareMode::Always)
        return AlphaTestResult::Pass;
      if (comp0 == CompareMode::Never && comp1 == CompareMode::Never)
        return AlphaTestResult::Fail;
      break;

    case AlphaTestOp::Xor:
      if ((comp0 == CompareMode::Always && comp1 == CompareMode::Never) ||
          (comp0 == CompareMode::Never && comp1 == CompareMode::Always))
        return AlphaTestResult::Pass;
      if ((comp0 == CompareMode::Always && comp1 == CompareMode::Always) ||
          (comp0 == CompareMode::Never && comp1 == CompareMode::Never))
        return AlphaTestResult::Fail;
      break;

    case AlphaTestOp::Xnor:
      if ((comp0 == CompareMode::Always && comp1 == CompareMode::Never) ||
          (comp0 == CompareMode::Never && comp1 == CompareMode::Always))
        return AlphaTestResult::Fail;
      if ((comp0 == CompareMode::Always && comp1 == CompareMode::Always) ||
          (comp0 == CompareMode::Never && comp1 == CompareMode::Never))
        return AlphaTestResult::Pass;
      break;

    default:
      return AlphaTestResult::Undetermined;
    }
    return AlphaTestResult::Undetermined;
  }
};
template <>
struct fmt::formatter<AlphaTest>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const AlphaTest& test, FormatContext& ctx)
  {
    return format_to(ctx.out(),
                     "Test 1: {} (ref: 0x{:02x})\n"
                     "Test 2: {} (ref: 0x{:02x})\n"
                     "Logic: {}\n",
                     test.comp0, test.ref0, test.comp1, test.ref1, test.logic);
  }
};

enum class FrameToField : u32
{
  Progressive = 0,
  InterlacedEven = 2,
  InterlacedOdd = 3,
};
template <>
struct fmt::formatter<FrameToField> : EnumFormatter<FrameToField::InterlacedOdd>
{
  static constexpr array_type names = {"Progressive", nullptr, "Interlaced (even lines)",
                                       "Interlaced (odd lines)"};
  formatter() : EnumFormatter(names) {}
};

union UPE_Copy
{
  u32 Hex;

  BitField<0, 1, bool, u32> clamp_top;      // if set clamp top
  BitField<1, 1, bool, u32> clamp_bottom;   // if set clamp bottom
  BitField<2, 1, bool, u32> yuv;            // if set, color conversion from RGB to YUV
  BitField<3, 4, u32> target_pixel_format;  // realformat is (fmt/2)+((fmt&1)*8).... for some reason
                                            // the msb is the lsb (pattern: cycling right shift)
  // gamma correction.. 0 = 1.0 ; 1 = 1.7 ; 2 = 2.2 ; 3 is reserved
  BitField<7, 2, u32> gamma;
  // "mipmap" filter... false = no filter (scale 1:1) ; true = box filter (scale 2:1)
  BitField<9, 1, bool, u32> half_scale;
  BitField<10, 1, bool, u32> scale_invert;  // if set vertical scaling is on
  BitField<11, 1, bool, u32> clear;
  BitField<12, 2, FrameToField> frame_to_field;
  BitField<14, 1, bool, u32> copy_to_xfb;
  BitField<15, 1, bool, u32> intensity_fmt;  // if set, is an intensity format (I4,I8,IA4,IA8)
  // if false automatic color conversion by texture format and pixel type
  BitField<16, 1, bool, u32> auto_conv;

  EFBCopyFormat tp_realFormat() const
  {
    return static_cast<EFBCopyFormat>(target_pixel_format / 2 + (target_pixel_format & 1) * 8);
  }
};
template <>
struct fmt::formatter<UPE_Copy>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const UPE_Copy& copy, FormatContext& ctx)
  {
    static constexpr std::array<const char*, 2> no_yes = {"No", "Yes"};
    std::string_view clamp;
    if (copy.clamp_top)
    {
      if (copy.clamp_bottom)
        clamp = "Top and Bottom";
      else
        clamp = "Top only";
    }
    else
    {
      if (copy.clamp_bottom)
        clamp = "Bottom only";
      else
        clamp = "None";
    }
    std::string_view gamma = "Invalid";
    switch (copy.gamma)
    {
    case 0:
      gamma = "1.0";
      break;
    case 1:
      gamma = "1.7";
      break;
    case 2:
      gamma = "2.2";
      break;
    }

    return format_to(ctx.out(),
                     "Clamping: {}\n"
                     "Converting from RGB to YUV: {}\n"
                     "Target pixel format: {}\n"
                     "Gamma correction: {}\n"
                     "Half scale: {}\n"
                     "Vertical scaling: {}\n"
                     "Clear: {}\n"
                     "Frame to field: {}\n"
                     "Copy to XFB: {}\n"
                     "Intensity format: {}\n"
                     "Automatic color conversion: {}",
                     clamp, no_yes[copy.yuv], copy.tp_realFormat(), gamma, no_yes[copy.half_scale],
                     no_yes[copy.scale_invert], no_yes[copy.clear], copy.frame_to_field,
                     no_yes[copy.copy_to_xfb], no_yes[copy.intensity_fmt], no_yes[copy.auto_conv]);
  }
};

union CopyFilterCoefficients
{
  using Values = std::array<u8, 7>;

  u64 Hex;

  BitField<0, 6, u64> w0;
  BitField<6, 6, u64> w1;
  BitField<12, 6, u64> w2;
  BitField<18, 6, u64> w3;
  BitField<32, 6, u64> w4;
  BitField<38, 6, u64> w5;
  BitField<44, 6, u64> w6;

  Values GetCoefficients() const
  {
    return {{
        static_cast<u8>(w0),
        static_cast<u8>(w1),
        static_cast<u8>(w2),
        static_cast<u8>(w3),
        static_cast<u8>(w4),
        static_cast<u8>(w5),
        static_cast<u8>(w6),
    }};
  }
};

union BPU_PreloadTileInfo
{
  BitField<0, 15, u32> count;
  BitField<15, 2, u32> type;
  u32 hex;
};
template <>
struct fmt::formatter<BPU_PreloadTileInfo>
{
  constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
  template <typename FormatContext>
  auto format(const BPU_PreloadTileInfo& info, FormatContext& ctx)
  {
    return format_to(ctx.out(), "Type: {}\nCount: {}", info.type, info.count);
  }
};

struct BPS_TmemConfig
{
  u32 preload_addr;
  u32 preload_tmem_even;
  u32 preload_tmem_odd;
  BPU_PreloadTileInfo preload_tile_info;
  u32 tlut_src;
  u32 tlut_dest;
  u32 texinvalidate;
};

union AllTexUnits;

// The addressing of the texture units is a bit non-obvious.
// This struct abstracts the complexity away.
union TexUnitAddress
{
  enum class Register : u32
  {
    SETMODE0 = 0,
    SETMODE1 = 1,
    SETIMAGE0 = 2,
    SETIMAGE1 = 3,
    SETIMAGE2 = 4,
    SETIMAGE3 = 5,
    SETTLUT = 6,
    UNKNOWN = 7,
  };

  BitField<0, 2, u32> UnitIdLow;
  BitField<2, 3, Register> Reg;
  BitField<5, 1, u32> UnitIdHigh;

  BitField<0, 6, u32> FullAddress;
  u32 hex;

  TexUnitAddress() : hex(0) {}
  TexUnitAddress(u32 unit_id, Register reg = Register::SETMODE0) : hex(0)
  {
    UnitIdLow = unit_id & 3;
    UnitIdHigh = unit_id >> 2;
    Reg = reg;
  }

  static TexUnitAddress FromBPAddress(u32 Address)
  {
    TexUnitAddress Val;
    // Clear upper two bits (which should always be 0x80)
    Val.FullAddress = Address & 0x3f;
    return Val;
  }

  u32 GetUnitID() const { return UnitIdLow | (UnitIdHigh << 2); }

private:
  friend AllTexUnits;

  size_t GetOffset() const { return FullAddress; }
  size_t GetBPAddress() const { return FullAddress | 0x80; }

  static constexpr size_t ComputeOffset(u32 unit_id)
  {
    // FIXME: Would be nice to construct a TexUnitAddress and get its offset,
    // but that doesn't seem to be possible in c++17

    // So we manually re-implement the calculation
    return (unit_id & 3) | ((unit_id & 4) << 3);
  }
};
static_assert(sizeof(TexUnitAddress) == sizeof(u32));

// A view of the registers of a single TexUnit
struct TexUnit
{
  TexMode0 texMode0;
  u32 : 32;  // doing u32 : 96 is legal according to the standard, but msvc
  u32 : 32;  // doesn't like it. So we stack multiple lines of u32 : 32;
  u32 : 32;
  TexMode1 texMode1;
  u32 : 32;
  u32 : 32;
  u32 : 32;
  TexImage0 texImage0;
  u32 : 32;
  u32 : 32;
  u32 : 32;
  TexImage1 texImage1;
  u32 : 32;
  u32 : 32;
  u32 : 32;
  TexImage2 texImage2;
  u32 : 32;
  u32 : 32;
  u32 : 32;
  TexImage3 texImage3;
  u32 : 32;
  u32 : 32;
  u32 : 32;
  TexTLUT texTlut;
  u32 : 32;
  u32 : 32;
  u32 : 32;
  u32 unknown;
};
static_assert(sizeof(TexUnit) == sizeof(u32) * 4 * 7 + sizeof(u32));

union AllTexUnits
{
  std::array<u32, 8 * 8> AllRegisters;

  const TexUnit& GetUnit(u32 UnitId) const
  {
    auto address = TexUnitAddress(UnitId);
    const u32* ptr = &AllRegisters[address.GetOffset()];
    return *reinterpret_cast<const TexUnit*>(ptr);
  }

private:
  // For debuggers since GetUnit can be optimised out in release builds
  template <u32 UnitId>
  struct TexUnitPadding
  {
    static_assert(UnitId != 0, "Can't use 0 as sizeof(std::array<u32, 0>) != 0");
    std::array<u32, TexUnitAddress::ComputeOffset(UnitId)> pad;
  };

  TexUnit tex0;
  struct
  {
    TexUnitPadding<1> pad1;
    TexUnit tex1;
  };
  struct
  {
    TexUnitPadding<2> pad2;
    TexUnit tex2;
  };
  struct
  {
    TexUnitPadding<3> pad3;
    TexUnit tex3;
  };
  struct
  {
    TexUnitPadding<4> pad4;
    TexUnit tex4;
  };
  struct
  {
    TexUnitPadding<5> pad5;
    TexUnit tex5;
  };
  struct
  {
    TexUnitPadding<6> pad6;
    TexUnit tex6;
  };
  struct
  {
    TexUnitPadding<7> pad7;
    TexUnit tex7;
  };
};
static_assert(sizeof(AllTexUnits) == 8 * 8 * sizeof(u32));

// All of BP memory

struct BPCmd
{
  int address;
  int changes;
  int newvalue;
};

struct BPMemory
{
  GenMode genMode;
  u32 display_copy_filter[4];  // 01-04
  u32 unknown;                 // 05
  // indirect matrices (set by GXSetIndTexMtx, selected by TevStageIndirect::matrix_index)
  // abc form a 2x3 offset matrix, there's 3 such matrices
  // the 3 offset matrices can either be indirect type, S-type, or T-type
  // 6bit scale factor s is distributed across IND_MTXA/B/C.
  // before using matrices scale by 2^-(s-17)
  IND_MTX indmtx[3];               // 06-0e GXSetIndTexMtx, 2x3 matrices
  IND_IMASK imask;                 // 0f
  TevStageIndirect tevind[16];     // 10 GXSetTevIndirect
  X12Y12 scissorTL;                // 20
  X12Y12 scissorBR;                // 21
  LPSize lineptwidth;              // 22 line and point width
  u32 sucounter;                   // 23
  u32 rascounter;                  // 24
  TEXSCALE texscale[2];            // 25-26 GXSetIndTexCoordScale
  RAS1_IREF tevindref;             // 27 GXSetIndTexOrder
  TwoTevStageOrders tevorders[8];  // 28-2F
  TCoordInfo texcoords[8];         // 0x30 s,t,s,t,s,t,s,t...
  ZMode zmode;                     // 40
  BlendMode blendmode;             // 41
  ConstantAlpha dstalpha;          // 42
  PEControl zcontrol;              // 43 GXSetZCompLoc, GXPixModeSync
  FieldMask fieldmask;             // 44
  u32 drawdone;                    // 45, bit1=1 if end of list
  u32 unknown5;                    // 46 clock?
  u32 petoken;                     // 47
  u32 petokenint;                  // 48
  X10Y10 copyTexSrcXY;             // 49
  X10Y10 copyTexSrcWH;             // 4a
  u32 copyTexDest;                 // 4b// 4b == CopyAddress (GXDispCopy and GXTexCopy use it)
  u32 unknown6;                    // 4c
  u32 copyMipMapStrideChannels;  // 4d usually set to 4 when dest is single channel, 8 when dest is
                                 // 2 channel, 16 when dest is RGBA
  // also, doubles whenever mipmap box filter option is set (excent on RGBA). Probably to do with
  // number of bytes to look at when smoothing
  u32 dispcopyyscale;                 // 4e
  u32 clearcolorAR;                   // 4f
  u32 clearcolorGB;                   // 50
  u32 clearZValue;                    // 51
  UPE_Copy triggerEFBCopy;            // 52
  CopyFilterCoefficients copyfilter;  // 53,54
  u32 boundbox0;                      // 55
  u32 boundbox1;                      // 56
  u32 unknown7[2];                    // 57,58
  S32X10Y10 scissorOffset;            // 59
  u32 unknown8[6];                    // 5a,5b,5c,5d, 5e,5f
  BPS_TmemConfig tmem_config;         // 60-66
  u32 metric;                         // 67
  FieldMode fieldmode;                // 68
  u32 unknown10[7];                   // 69-6F
  u32 unknown11[16];                  // 70-7F
  AllTexUnits tex;                    // 80-bf
  TevStageCombiner combiners[16];     // 0xC0-0xDF
  TevReg tevregs[4];                  // 0xE0
  FogRangeParams fogRange;            // 0xE8
  FogParams fog;                      // 0xEE,0xEF,0xF0,0xF1,0xF2
  AlphaTest alpha_test;               // 0xF3
  ZTex1 ztex1;                        // 0xf4,0xf5
  ZTex2 ztex2;
  TevKSel tevksel[8];  // 0xf6,0xf7,f8,f9,fa,fb,fc,fd
  u32 bpMask;          // 0xFE
  u32 unknown18;       // ff

  bool UseEarlyDepthTest() const { return zcontrol.early_ztest && zmode.testenable; }
  bool UseLateDepthTest() const { return !zcontrol.early_ztest && zmode.testenable; }
};

#pragma pack()

extern BPMemory bpmem;

void LoadBPReg(u8 reg, u32 value, int cycles_into_future);
void LoadBPRegPreprocess(u8 reg, u32 value, int cycles_into_future);

std::pair<std::string, std::string> GetBPRegInfo(u8 cmd, u32 cmddata);
