/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2022 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#ifndef XENIA_GPU_SHADERS_TEXTURE_LOAD_XESLI_
#define XENIA_GPU_SHADERS_TEXTURE_LOAD_XESLI_

#include "endian.xesli"
#include "texture_address.xesli"

// 128 threads per group (the maximum wave size supported by DXIL and SPIR-V,
// and the minimum required number of threads per group on Vulkan), laid out as
// 4x32 (32 texels along Y per group - one guest tile) - starting with 64x32
// blocks (2x1 guest tiles) per group for 8bpb / 16bpb, and smaller for larger
// block sizes. Since the mip tail is packed in 32x / x32 storage, there's no
// need for the Y group size smaller than 32 - 8x16, for instance, would result
// in 128x16 blocks per group for 8bpb / 16bpb, and for a 32x32 mip tail, there
// would be two groups rather than one, for a total of 128x32 blocks - 75% of
// the work will be wasted rather than 50% with one 64x32-block group.
#define LOCAL_SIZE_X_XE 4
#define LOCAL_SIZE_Y_XE 32
#define LOCAL_SIZE_Z_XE 1

push_const_begin_xe(b0, space0)
  uint xe_texture_load_is_tiled_3d_endian_scale;
  // Base offset in bytes, resolution-scaled.
  uint xe_texture_load_guest_offset;
  // Unscaled.
  uint xe_texture_load_guest_pitch_aligned;
  // For 3D textures only (ignored otherwise) - aligned to 32, unscaled.
  uint xe_texture_load_guest_z_stride_block_rows_aligned;

  // - std140 vector boundary -

  // If this is a packed mip tail, this is aligned to tile dimensions.
  // Resolution-scaled.
  uint3_xe xe_texture_load_size_blocks;
  // Base offset in bytes.
  uint xe_texture_load_host_offset;

  // - std140 vector boundary -

  uint xe_texture_load_host_pitch;
  uint xe_texture_load_height_texels;
push_const_end_xe

#define XE_TEXTURE_LOAD_PUSH_CONST_BINDING push_const_binding_xe(buffer(0))

struct XeTextureLoadInfo {
  bool is_tiled;
  bool is_3d;
  uint endian_32;
  uint2_xe resolution_scale;
  uint guest_offset;
  uint guest_pitch_aligned;
  uint guest_z_stride_block_rows_aligned;
  uint3_xe size_blocks;
  uint host_offset;
  uint host_pitch;
  uint height_texels;
};

XeTextureLoadInfo XeTextureLoadGetInfo(param_push_consts_xe) {
  XeTextureLoadInfo load_info;
  uint is_tiled_3d_endian_scale =
      push_const_xe(xe_texture_load_is_tiled_3d_endian_scale);
  #ifdef XE_TEXTURE_LOAD_RESOLUTION_SCALED
    // Only resolved textures can be resolution-scaled, and resolving is only
    // possible to a tiled destination.
    load_info.is_tiled = true;
  #else
    load_info.is_tiled = (is_tiled_3d_endian_scale & 1u) != 0u;
  #endif
  load_info.is_3d = (is_tiled_3d_endian_scale & (1u << 1u)) != 0u;
  load_info.endian_32 = (is_tiled_3d_endian_scale >> 2u) & 3u;
  #ifdef XE_TEXTURE_LOAD_RESOLUTION_SCALED
    load_info.resolution_scale =
        ((uint_x2_xe(is_tiled_3d_endian_scale)) >> uint2_xe(4u, 7u)) & 7u;
  #else
    load_info.resolution_scale = uint2_xe(1u, 1u);
  #endif
  load_info.guest_offset = push_const_xe(xe_texture_load_guest_offset);
  load_info.guest_pitch_aligned =
      push_const_xe(xe_texture_load_guest_pitch_aligned);
  load_info.guest_z_stride_block_rows_aligned =
      push_const_xe(xe_texture_load_guest_z_stride_block_rows_aligned);
  load_info.size_blocks = push_const_xe(xe_texture_load_size_blocks);
  load_info.host_offset = push_const_xe(xe_texture_load_host_offset);
  load_info.host_pitch = push_const_xe(xe_texture_load_host_pitch);
  load_info.height_texels = push_const_xe(xe_texture_load_height_texels);
  return load_info;
}

uint XeTextureLoadSourceAddress(const XeTextureLoadInfo load_info,
                                const uint3_xe host_position,
                                const uint bytes_per_block_log2) {
  uint address;
  uint3_xe guest_position = host_position;
#ifdef XE_TEXTURE_LOAD_RESOLUTION_SCALED
  const XeniaTextureResolutionScaledAddressing resolution_scaled_addressing =
      XeniaTextureGetResolutionScaledAddressing(host_position.xy,
                                                load_info.resolution_scale,
                                                bytes_per_block_log2);
  guest_position.xy = resolution_scaled_addressing.guest_group_origin;
#else
  dont_flatten_xe if (!load_info.is_tiled) {
    address = (guest_position.x +
               load_info.guest_pitch_aligned *
                   (guest_position.y +
                    load_info.guest_z_stride_block_rows_aligned *
                        guest_position.z)) <<
              bytes_per_block_log2;
  } else
#endif
  {
    dont_flatten_xe if (load_info.is_3d) {
      address = uint(XenosTextureTiledAddress3D(
          int3_xe(guest_position),
          load_info.guest_pitch_aligned >> XENOS_TEXTURE_MACRO_TILE_WIDTH_LOG2,
          load_info.guest_z_stride_block_rows_aligned >>
              XENOS_TEXTURE_MACRO_TILE_HEIGHT_3D_LOG2,
          bytes_per_block_log2));
    } else {
      address = uint(XenosTextureTiledAddress2D(
          int2_xe(guest_position.xy),
          load_info.guest_pitch_aligned >> XENOS_TEXTURE_MACRO_TILE_WIDTH_LOG2,
          bytes_per_block_log2));
    }
  }
#ifdef XE_TEXTURE_LOAD_RESOLUTION_SCALED
  address =
      address * (load_info.resolution_scale.x * load_info.resolution_scale.y) +
      resolution_scaled_addressing.host_byte_offset_in_guest_group;
#endif
  address += load_info.guest_offset;
  return address;
}

// XOR to apply to the byte address to flip the bits corresponding to the given
// X coordinate bits within:
// - Resolution-scaled tiled: XeniaTextureResolutionScaledGroupBlocks.x;
// - Unscaled tiled: macro tile width;
// - Linear: 256 bytes.
// Addition is recommended instead of XOR if the bits are known to be 0 in the
// original address, so the host GPU driver can optimize it into a constant load
// offset if available in the host hardware shader instruction set architecture.
uint XeTextureLoadLocalXAddressXor(const uint x,
                                   const uint bytes_per_block_log2,
                                   const bool is_tiled) {
  uint x_address_xor;
#ifndef XE_TEXTURE_LOAD_RESOLUTION_SCALED
  dont_flatten_xe if (is_tiled) {
    x_address_xor = uint(
        XenosTextureTiledAddressXInMacroXor(int(x), bytes_per_block_log2));
  } else
#endif
  {
    x_address_xor = x << bytes_per_block_log2;
  }
  return x_address_xor;
}

#endif  // XENIA_GPU_SHADERS_TEXTURE_LOAD_XESLI_
