/**
 ******************************************************************************
 * 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 xesl_localSize_x 4
#define xesl_localSize_y 32
#define xesl_localSize_z 1

xesl_constantBuffer_begin(xe_texture_load_constants, set=2, binding=0, b0,
                          space0)
  uint xe_texture_load_is_tiled_3d_endian_scale;
  // Base offset in bytes, resolution-scaled.
  uint xe_texture_load_guest_offset;
  // For tiled textures - row pitch in guest blocks, aligned to 32, unscaled.
  // For linear textures - row pitch in bytes.
  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.
  xesl_uint3 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;
xesl_constantBuffer_end(xe_texture_load_constants)

#define XE_TEXTURE_LOAD_CONSTANT_BUFFER_BINDING \
    xesl_constantBuffer_binding(xe_texture_load_constants, buffer(0))

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

XeTextureLoadInfo XeTextureLoadGetInfo(
    xesl_function_param_constantBuffer(xe_texture_load_constants)) {
  XeTextureLoadInfo load_info;
  uint is_tiled_3d_endian_scale = xesl_constant(
      xe_texture_load_constants, 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 =
        ((xesl_uint_x2(is_tiled_3d_endian_scale)) >> xesl_uint2(4u, 7u)) & 7u;
  #else
    load_info.resolution_scale = xesl_uint2(1u, 1u);
  #endif
  load_info.guest_offset =
      xesl_constant(xe_texture_load_constants, xe_texture_load_guest_offset);
  load_info.guest_pitch_aligned = xesl_constant(
      xe_texture_load_constants, xe_texture_load_guest_pitch_aligned);
  load_info.guest_z_stride_block_rows_aligned =
      xesl_constant(xe_texture_load_constants,
                    xe_texture_load_guest_z_stride_block_rows_aligned);
  load_info.size_blocks =
      xesl_constant(xe_texture_load_constants, xe_texture_load_size_blocks);
  load_info.host_offset =
      xesl_constant(xe_texture_load_constants, xe_texture_load_host_offset);
  load_info.host_pitch =
      xesl_constant(xe_texture_load_constants, xe_texture_load_host_pitch);
  load_info.height_texels =
      xesl_constant(xe_texture_load_constants, xe_texture_load_height_texels);
  return load_info;
}

// bpb and bpb_log2 are separate because bpb may be not a power of 2 (like 96).
uint XeTextureLoadGuestBlockOffset(XeTextureLoadInfo load_info,
                                   xesl_uint3 block_index, uint bpb,
                                   uint bpb_log2) {
  #ifdef XE_TEXTURE_LOAD_RESOLUTION_SCALED
    // Only resolved textures can be resolution-scaled, and resolving is only
    // possible to a tiled destination.
    return
        load_info.guest_offset +
        XeTextureScaledTiledOffset(load_info.is_3d, block_index,
                                   load_info.guest_pitch_aligned,
                                   load_info.guest_z_stride_block_rows_aligned,
                                   bpb_log2, load_info.resolution_scale);
  #else
    int block_offset_guest;
    xesl_dont_flatten if (load_info.is_tiled) {
      xesl_dont_flatten if (load_info.is_3d) {
        block_offset_guest = XeTextureTiledOffset3D(
            xesl_int3(block_index), load_info.guest_pitch_aligned,
            load_info.guest_z_stride_block_rows_aligned, bpb_log2);
      } else {
        block_offset_guest = XeTextureTiledOffset2D(
            xesl_int2(block_index.xy), load_info.guest_pitch_aligned,
            bpb_log2);
      }
    } else {
      block_offset_guest = XeTextureGuestLinearOffset(
          xesl_int3(block_index), load_info.guest_pitch_aligned,
          load_info.guest_z_stride_block_rows_aligned, bpb);
    }
    return uint(int(load_info.guest_offset) + block_offset_guest);
  #endif
}

// Offset of the beginning of the odd R32G32/R32G32B32A32 load address from the
// address of the even load, for power-of-two-sized textures.
uint XeTextureLoadRightConsecutiveBlocksOffset(XeTextureLoadInfo load_info,
                                               uint block_x, uint bpb_log2) {
  #ifdef XE_TEXTURE_LOAD_RESOLUTION_SCALED
    return XeTextureScaledRightSubUnitOffsetInConsecutivePair(
               block_x, bpb_log2, load_info.resolution_scale);
  #else
    uint offset;
    uint consecutive_blocks_log2 =
        XeTextureTiledConsecutiveBlocksLog2(bpb_log2);
    xesl_dont_flatten if (load_info.is_tiled) {
      offset = XeTextureTiledOddConsecutiveBlocksOffset(bpb_log2);
    } else {
      offset = 1u << (consecutive_blocks_log2 + bpb_log2);
    }
    return offset;
  #endif
}

#endif  // XENIA_GPU_SHADERS_TEXTURE_LOAD_XESLI_
