/*
 * Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
 *
 * SPDX-License-Identifier: MIT
 */

#include <hip/hip_runtime.h>
#include <hip/texture_types.h>
#include "hip_internal.hpp"
#include "hip_platform.hpp"
#include "hip_conversions.hpp"
#include "platform/sampler.hpp"

struct __hip_texture {
  uint32_t imageSRD[HIP_IMAGE_OBJECT_SIZE_DWORD];
  uint32_t samplerSRD[HIP_SAMPLER_OBJECT_SIZE_DWORD];
  amd::Image* image;
  amd::Sampler* sampler;
  hipResourceDesc resDesc;
  hipTextureDesc texDesc;
  hipResourceViewDesc resViewDesc;

  __hip_texture(amd::Image* image_, amd::Sampler* sampler_, const hipResourceDesc& resDesc_,
                const hipTextureDesc& texDesc_, const hipResourceViewDesc& resViewDesc_)
      : image(image_),
        sampler(sampler_),
        resDesc(resDesc_),
        texDesc(texDesc_),
        resViewDesc(resViewDesc_) {
    amd::Context& context = *hip::getCurrentDevice()->asContext();
    amd::Device& device = *context.devices()[0];

    device::Memory* imageMem = image->getDeviceMemory(device);
    std::memcpy(imageSRD, imageMem->cpuSrd(), sizeof(imageSRD));

    device::Sampler* samplerMem = sampler->getDeviceSampler(device);
    std::memcpy(samplerSRD, samplerMem->hwState(), sizeof(samplerSRD));
  }
};

namespace hip {

hipError_t ihipFree(void* ptr);
amd::Image* ihipImageCreate(const cl_channel_order channelOrder, const cl_channel_type channelType,
                            const cl_mem_object_type imageType, const size_t imageWidth,
                            const size_t imageHeight, const size_t imageDepth,
                            const size_t imageArraySize, const size_t imageRowPitch,
                            const size_t imageSlicePitch, const uint32_t numMipLevels,
                            const size_t offset, amd::Memory* buffer, hipError_t& status);

static bool ValidateDevicePointer(const void* ptr) {
  amd::Device* curDev = hip::getCurrentDevice()->devices()[0];
  // host pointers can be GPU accessible on APUs but should be denied for texture APIs as they
  // aren't valid device pointers
  return amd::MemObjMap::FindMemObj(ptr, nullptr, curDev) != nullptr;
}

hipError_t ihipCreateTextureObject(hipTextureObject_t* pTexObject, const hipResourceDesc* pResDesc,
                                   const hipTextureDesc* pTexDesc,
                                   const hipResourceViewDesc* pResViewDesc) {
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    return hipErrorNotSupported;
  }

  // Validate input params
  if (pTexObject == nullptr || pResDesc == nullptr || pTexDesc == nullptr) {
    return hipErrorInvalidChannelDescriptor;
  }

  // pResViewDesc can only be specified if the type of resource is a HIP array or a HIP mipmapped
  // array.
  if ((pResViewDesc != nullptr) && ((pResDesc->resType != hipResourceTypeArray) &&
                                    (pResDesc->resType != hipResourceTypeMipmappedArray))) {
    return hipErrorUnknown;
  }

  // If hipResourceDesc::resType is set to hipResourceTypeArray,
  if (pResDesc->resType == hipResourceTypeArray) {
    // hipResourceDesc::res::array::array must be set to a valid HIP array handle.
    if (pResDesc->res.array.array == nullptr) {
      return hipErrorInvalidValue;
    } else if (pResDesc->res.array.array->depth > 0 &&
               pTexDesc->filterMode == hipFilterModeLinear &&
               !strncmp(info.name_, "gfx90a", strlen("gfx90a"))) {
      LogPrintfInfo("%s doesn't support 3D linear filter!", info.name_);
      return hipErrorNotSupported;
    }
  }

  // If hipResourceDesc::resType is set to hipResourceTypeMipmappedArray,
  // hipResourceDesc::res::mipmap::mipmap must be set to a valid HIP mipmapped array handle
  // and hipTextureDesc::normalizedCoords must be set to true.
  if (pResDesc->resType == hipResourceTypeMipmappedArray) {
    bool mipMapSupport = true;
    amd::Context& context = *hip::getCurrentDevice()->asContext();
    const std::vector<amd::Device*>& devices = context.devices();
    for (auto& dev : devices) {
      if (!dev->settings().checkExtension(ClKhrMipMapImage)) {
        mipMapSupport = false;  // Now PAL-backend can support mipmap, Rocm-backend cannot.
      }
    }
    if (mipMapSupport == false) {
      LogInfo("Mipmap not supported on the device");
      return hipErrorNotSupported;
    }
    if (pResDesc->res.mipmap.mipmap == nullptr) {
      return hipErrorInvalidValue;
    }
    if (pTexDesc->normalizedCoords == 0) {
      return hipErrorInvalidChannelDescriptor;
    }
  }

  // If hipResourceDesc::resType is set to hipResourceTypeLinear,
  // hipResourceDesc::res::linear::devPtr must be set to a valid device pointer, that is aligned to
  // hipDeviceProp::textureAlignment. The total number of elements in the linear address range
  // cannot exceed hipDeviceProp::maxTexture1DLinear.
  if ((pResDesc->resType == hipResourceTypeLinear) &&
      ((pResDesc->res.linear.devPtr == nullptr) ||
       (!amd::isMultipleOf(pResDesc->res.linear.devPtr, info.imageBaseAddressAlignment_)) ||
       (pResDesc->res.linear.sizeInBytes >=
        info.imageMaxBufferSize_ * hip::getElementSize(pResDesc->res.linear.desc)))) {
    return hipErrorInvalidChannelDescriptor;
  }

  // If hipResourceDesc::resType is set to hipResourceTypePitch2D,
  // hipResourceDesc::res::pitch2D::devPtr must be set to a valid device pointer, that is aligned to
  // hipDeviceProp::textureAlignment. hipResourceDesc::res::pitch2D::width and
  // hipResourceDesc::res::pitch2D::height specify the width and height of the array in elements,
  // and cannot exceed hipDeviceProp::maxTexture2DLinear[0] and hipDeviceProp::maxTexture2DLinear[1]
  // respectively. hipResourceDesc::res::pitch2D::pitchInBytes specifies the pitch between two rows
  // in bytes and has to be aligned to hipDeviceProp::texturePitchAlignment. Pitch cannot exceed
  // hipDeviceProp::maxTexture2DLinear[2].
  if ((pResDesc->resType == hipResourceTypePitch2D) &&
      ((pResDesc->res.pitch2D.devPtr == nullptr) ||
       (!amd::isMultipleOf(pResDesc->res.pitch2D.devPtr, info.imageBaseAddressAlignment_)) ||
       (pResDesc->res.pitch2D.width > info.image2DMaxWidth_) ||
       (pResDesc->res.pitch2D.height > info.image2DMaxHeight_) ||
       (!amd::isMultipleOf(pResDesc->res.pitch2D.pitchInBytes, info.imagePitchAlignment_)))) {
    // TODO check pitch limits.
    return hipErrorInvalidValue;
  }

  // We don't program the max_ansio_ratio field in the the HW sampler SRD.
  if (pTexDesc->maxAnisotropy != 0) {
    return hipErrorNotSupported;
  }
  // We don't program the lod_bias field in the HW sampler SRD.
  if (pTexDesc->mipmapLevelBias != 0) {
    LogError("mipmapLevelBias not supported!");
    return hipErrorNotSupported;
  }
  // We don't program the min_lod field in the HW sampler SRD.
  if (pTexDesc->minMipmapLevelClamp != 0) {
    LogInfo("minMipmapLevelClamp ignored!");
  }
  // We don't program the max_lod field in the HW sampler SRD.
  if (pTexDesc->maxMipmapLevelClamp != 0) {
    LogInfo("maxMipmapLevelClamp ignored!");
  }

  // TODO ROCclr assumes all dimensions have the same addressing mode.
  cl_addressing_mode addressMode[3] = {CL_ADDRESS_NONE, CL_ADDRESS_NONE, CL_ADDRESS_NONE};
  // If hipTextureDesc::normalizedCoords is set to zero,
  // hipAddressModeWrap and hipAddressModeMirror won't be supported
  // and will be switched to hipAddressModeClamp.
  for (int i = 0; i < 3; i++) {
    if ((pTexDesc->normalizedCoords == 0) && ((pTexDesc->addressMode[i] == hipAddressModeWrap) ||
                                              (pTexDesc->addressMode[i] == hipAddressModeMirror))) {
      addressMode[i] = hip::getCLAddressingMode(hipAddressModeClamp);
    }
    // hipTextureDesc::addressMode is ignored if hipResourceDesc::resType is hipResourceTypeLinear
    else if (pResDesc->resType != hipResourceTypeLinear) {
      addressMode[i] = hip::getCLAddressingMode(pTexDesc->addressMode[i]);
    }
  }

#ifndef CL_FILTER_NONE
#define CL_FILTER_NONE 0x1142
#endif
  cl_filter_mode filterMode = CL_FILTER_NONE;
  cl_filter_mode mipFilterMode = CL_FILTER_NONE;
#undef CL_FILTER_NONE
  // hipTextureDesc::filterMode is ignored if hipResourceDesc::resType is hipResourceTypeLinear.
  if (pResDesc->resType != hipResourceTypeLinear) {
    filterMode = hip::getCLFilterMode(pTexDesc->filterMode);
  }

  if (pResDesc->resType == hipResourceTypeMipmappedArray) {
    mipFilterMode = hip::getCLFilterMode(pTexDesc->mipmapFilterMode);
  }

  auto sampler_deleter = [](amd::Sampler* s) { s->release(); };
  std::unique_ptr<amd::Sampler, decltype(sampler_deleter)> sampler(
      new amd::Sampler(*hip::getCurrentDevice()->asContext(), pTexDesc->normalizedCoords,
                       addressMode, filterMode, mipFilterMode, pTexDesc->minMipmapLevelClamp,
                       pTexDesc->maxMipmapLevelClamp),
      sampler_deleter);

  if (sampler.get() == nullptr) {
    return hipErrorOutOfMemory;
  }

  if (!sampler.get()->create()) {
    return hipErrorOutOfMemory;
  }

  amd::Image* image = nullptr;
  switch (pResDesc->resType) {
    case hipResourceTypeArray: {
      cl_mem memObj = reinterpret_cast<cl_mem>(pResDesc->res.array.array->data);
      if (!is_valid(memObj)) {
        return hipErrorInvalidValue;
      }
      image = as_amd(memObj)->asImage();

      hipTextureReadMode readMode = pTexDesc->readMode;
      // 32-bit integer format will not be promoted, regardless of whether or not
      // this hipTextureDesc::readMode is set hipReadModeNormalizedFloat is specified.
      if ((pResDesc->res.array.array->Format == HIP_AD_FORMAT_SIGNED_INT32) ||
          (pResDesc->res.array.array->Format == HIP_AD_FORMAT_UNSIGNED_INT32)) {
        readMode = hipReadModeElementType;
      }

      // We need to create an image view if the user requested to use normalized pixel values,
      // due to already having the image created with a different format.
      if ((pResViewDesc != nullptr) || (readMode == hipReadModeNormalizedFloat) ||
          (pTexDesc->sRGB == 1)) {
        // TODO ROCclr currently right now can only change the format of the image.
        const cl_channel_order channelOrder =
            (pResViewDesc != nullptr)
                ? hip::getCLChannelOrder(hip::getNumChannels(pResViewDesc->format), pTexDesc->sRGB)
                : hip::getCLChannelOrder(pResDesc->res.array.array->NumChannels, pTexDesc->sRGB);
        const cl_channel_type channelType =
            (pResViewDesc != nullptr)
                ? hip::getCLChannelType(hip::getArrayFormat(pResViewDesc->format), readMode)
                : hip::getCLChannelType(pResDesc->res.array.array->Format, readMode);
        const amd::Image::Format imageFormat(cl_image_format{channelOrder, channelType});
        if (!imageFormat.isValid()) {
          return hipErrorInvalidValue;
        }

        image = image->createView(*hip::getCurrentDevice()->asContext(), imageFormat, nullptr);
        if (image == nullptr) {
          return hipErrorInvalidValue;
        }
      } else {
        image->retain();  // will be released in ihipDestroyTextureObject()
      }
      break;
    }
    case hipResourceTypeMipmappedArray: {
      cl_mem memObj = reinterpret_cast<cl_mem>(pResDesc->res.array.array->data);
      if (!is_valid(memObj)) {
        return hipErrorInvalidValue;
      }
      image = as_amd(memObj)->asImage();

      hipTextureReadMode readMode = pTexDesc->readMode;
      // 32-bit integer format will not be promoted, regardless of whether or not
      // this hipTextureDesc::readMode is set hipReadModeNormalizedFloat is specified.
      if ((pResDesc->res.array.array->Format == HIP_AD_FORMAT_SIGNED_INT32) ||
          (pResDesc->res.array.array->Format == HIP_AD_FORMAT_UNSIGNED_INT32)) {
        readMode = hipReadModeElementType;
      }

      // We need to create an image view if the user requested to use normalized pixel values,
      // due to already having the image created with a different format.
      if ((pResViewDesc != nullptr) || (readMode == hipReadModeNormalizedFloat) ||
          (pTexDesc->sRGB == 1)) {
        // TODO ROCclr currently right now can only change the format of the image.
        const cl_channel_order channelOrder =
            (pResViewDesc != nullptr)
                ? hip::getCLChannelOrder(hip::getNumChannels(pResViewDesc->format), pTexDesc->sRGB)
                : hip::getCLChannelOrder(pResDesc->res.mipmap.mipmap->num_channels, pTexDesc->sRGB);
        const cl_channel_type channelType =
            (pResViewDesc != nullptr)
                ? hip::getCLChannelType(hip::getArrayFormat(pResViewDesc->format), readMode)
                : hip::getCLChannelType(pResDesc->res.mipmap.mipmap->format, readMode);
        const amd::Image::Format imageFormat(cl_image_format{channelOrder, channelType});
        if (!imageFormat.isValid()) {
          return hipErrorInvalidValue;
        }

        image = image->createView(*hip::getCurrentDevice()->asContext(), imageFormat, nullptr, 0, 0,
                                  true);
        if (image == nullptr) {
          return hipErrorInvalidValue;
        }
      } else {
        image->retain();  // will be released in ihipDestroyTextureObject()
      }
      break;
    }
    case hipResourceTypeLinear: {
      const cl_channel_order channelOrder =
          hip::getCLChannelOrder(hip::getNumChannels(pResDesc->res.linear.desc), pTexDesc->sRGB);
      const cl_channel_type channelType =
          hip::getCLChannelType(hip::getArrayFormat(pResDesc->res.linear.desc), pTexDesc->readMode);
      const amd::Image::Format imageFormat({channelOrder, channelType});
      const cl_mem_object_type imageType = hip::getCLMemObjectType(pResDesc->resType);
      const size_t imageSizeInBytes = pResDesc->res.linear.sizeInBytes;
      amd::Memory* buffer = getMemoryObjectWithOffset(hip::getCurrentDevice(),
                                                       pResDesc->res.linear.devPtr,
                                                       imageSizeInBytes);
      hipError_t status = hipSuccess;
      image = ihipImageCreate(channelOrder, channelType, imageType,
                              imageSizeInBytes / imageFormat.getElementSize(), /* imageWidth */
                              0,                                               /* imageHeight */
                              0,                                               /* imageDepth */
                              0,                                               /* imageArraySize */
                              0,                                               /* imageRowPitch */
                              0,                                               /* imageSlicePitch */
                              0,                                               /* numMipLevels */
                              0,                                               /* offset */
                              buffer, status);
      if (buffer != nullptr) {
        buffer->release();
      }

      if (image == nullptr) {
        return status;
      }
      break;
    }
    case hipResourceTypePitch2D: {
      const cl_channel_order channelOrder =
          hip::getCLChannelOrder(hip::getNumChannels(pResDesc->res.pitch2D.desc), pTexDesc->sRGB);
      const cl_channel_type channelType = hip::getCLChannelType(
          hip::getArrayFormat(pResDesc->res.pitch2D.desc), pTexDesc->readMode);
      const amd::Image::Format imageFormat({channelOrder, channelType});
      const cl_mem_object_type imageType = hip::getCLMemObjectType(pResDesc->resType);
      // Guard against unsigned underflow when height is 0
      const size_t imageSizeInBytes = (pResDesc->res.pitch2D.height > 0)
          ? pResDesc->res.pitch2D.width * imageFormat.getElementSize() +
            pResDesc->res.pitch2D.pitchInBytes * (pResDesc->res.pitch2D.height - 1)
          : pResDesc->res.pitch2D.width * imageFormat.getElementSize();
      if (!ValidateDevicePointer(pResDesc->res.pitch2D.devPtr)) {
        return hipErrorInvalidValue;
      }
      amd::Memory* buffer = getMemoryObjectWithOffset(hip::getCurrentDevice(),
                                                       pResDesc->res.pitch2D.devPtr,
                                                       imageSizeInBytes);

      hipError_t status = hipSuccess;
      image = ihipImageCreate(channelOrder, channelType, imageType,
                              pResDesc->res.pitch2D.width,        /* imageWidth */
                              pResDesc->res.pitch2D.height,       /* imageHeight */
                              0,                                  /* imageDepth */
                              0,                                  /* imageArraySize */
                              pResDesc->res.pitch2D.pitchInBytes, /* imageRowPitch */
                              0,                                  /* imageSlicePitch */
                              0,                                  /* numMipLevels */
                              0,                                  /* offset */
                              buffer, status);
      if (buffer != nullptr) {
        buffer->release();
      }
      if (image == nullptr) {
        return status;
      }
      break;
    }
  }

  void* texObjectBuffer = nullptr;
  hipError_t err =
      ihipMalloc(&texObjectBuffer, sizeof(__hip_texture), CL_MEM_SVM_FINE_GRAIN_BUFFER);
  if (texObjectBuffer == nullptr || err != hipSuccess) {
    return hipErrorOutOfMemory;
  }
  *pTexObject = new (texObjectBuffer)
      __hip_texture{image, sampler.release(), *pResDesc, *pTexDesc,
                    (pResViewDesc != nullptr) ? *pResViewDesc : hipResourceViewDesc{}};

  return hipSuccess;
}

hipError_t hipCreateTextureObject(hipTextureObject_t* pTexObject, const hipResourceDesc* pResDesc,
                                  const hipTextureDesc* pTexDesc,
                                  const hipResourceViewDesc* pResViewDesc) {
  HIP_INIT_API(hipCreateTextureObject, pTexObject, pResDesc, pTexDesc, pResViewDesc);

  HIP_RETURN(ihipCreateTextureObject(pTexObject, pResDesc, pTexDesc, pResViewDesc));
}

hipError_t ihipDestroyTextureObject(hipTextureObject_t texObject) {
  if (texObject == nullptr) {
    return hipSuccess;
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    return hipErrorNotSupported;
  }

  if (texObject != nullptr && texObject->image) {
    texObject->image->release();
  }

  // The texture object always owns the sampler SRD.
  texObject->sampler->release();

  // TODO Should call ihipFree() to not polute the api trace.
  return ihipFree(texObject);
}

hipError_t ihipUnbindTexture(textureReference* texRef) {
  hipError_t hip_error = hipSuccess;

  do {
    if (texRef == nullptr) {
      hip_error = hipErrorInvalidValue;
      break;
    }

    amd::Device* device = hip::getCurrentDevice()->devices()[0];
    const device::Info& info = device->info();
    if (!info.imageSupport_) {
      LogPrintfError("Texture not supported on the device %s", info.name_);
      HIP_RETURN(hipErrorNotSupported);
    }

    hip_error = ihipDestroyTextureObject(texRef->textureObject);
    if (hip_error != hipSuccess) {
      break;
    }

    const_cast<textureReference*>(texRef)->textureObject = nullptr;

  } while (0);

  return hip_error;
}

hipError_t hipDestroyTextureObject(hipTextureObject_t texObject) {
  HIP_INIT_API(hipDestroyTextureObject, texObject);

  HIP_RETURN(ihipDestroyTextureObject(texObject));
}

hipError_t ihipGetTextureObjectResourceDesc(hipResourceDesc* pResDesc,
                                            hipTextureObject_t texObject) {
  if ((pResDesc == nullptr) || (texObject == nullptr)) {
    return hipErrorInvalidValue;
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    return hipErrorNotSupported;
  }

  *pResDesc = texObject->resDesc;

  return hipSuccess;
}

hipError_t hipGetTextureObjectResourceDesc(hipResourceDesc* pResDesc,
                                           hipTextureObject_t texObject) {
  HIP_INIT_API(hipGetTextureObjectResourceDesc, pResDesc, texObject);

  HIP_RETURN(ihipGetTextureObjectResourceDesc(pResDesc, texObject));
}

hipError_t hipGetTextureObjectResourceViewDesc(hipResourceViewDesc* pResViewDesc,
                                               hipTextureObject_t texObject) {
  HIP_INIT_API(hipGetTextureObjectResourceViewDesc, pResViewDesc, texObject);

  if ((pResViewDesc == nullptr) || (texObject == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *pResViewDesc = texObject->resViewDesc;

  HIP_RETURN(hipSuccess);
}

hipError_t hipGetTextureObjectTextureDesc(hipTextureDesc* pTexDesc, hipTextureObject_t texObject) {
  HIP_INIT_API(hipGetTextureObjectTextureDesc, pTexDesc, texObject);

  if ((pTexDesc == nullptr) || (texObject == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *pTexDesc = texObject->texDesc;

  HIP_RETURN(hipSuccess);
}

inline hipError_t ihipGetTextureAlignmentOffset(size_t* offset, const void* devPtr) {
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    return hipErrorNotSupported;
  }

  const char* alignedDevPtr =
      amd::alignUp(static_cast<const char*>(devPtr), info.imageBaseAddressAlignment_);
  const size_t alignedOffset = alignedDevPtr - static_cast<const char*>(devPtr);

  // If the device memory pointer was returned from hipMalloc(),
  // the offset is guaranteed to be 0 and NULL may be passed as the offset parameter.
  if ((alignedOffset != 0) && (offset == nullptr)) {
    LogPrintfError("Texture object not aligned with offset %u", alignedOffset);
    return hipErrorInvalidValue;
  }

  if (offset != nullptr) {
    *offset = alignedOffset;
  }

  return hipSuccess;
}

hipError_t ihipBindTexture(size_t* offset, const textureReference* texref, const void* devPtr,
                           const hipChannelFormatDesc* desc, size_t size) {
  if (texref == nullptr) {
    return hipErrorUnknown;
  }

  if (devPtr == nullptr) {
    return hipErrorNotFound;
  }

  if (desc == nullptr) {
    return hipErrorInvalidValue;
  }

  // Any previous address or HIP array state associated with the texture reference is superseded by
  // this function. Any memory previously bound to hTexRef is unbound. No need to check for errors.
  hipError_t err = ihipDestroyTextureObject(texref->textureObject);
  if (err != hipSuccess) {
    return err;
  }

  hipResourceDesc resDesc = {};
  resDesc.resType = hipResourceTypeLinear;
  resDesc.res.linear.devPtr = const_cast<void*>(devPtr);
  resDesc.res.linear.desc = *desc;
  resDesc.res.linear.sizeInBytes = size;
  err = ihipGetTextureAlignmentOffset(offset, devPtr);
  if (err != hipSuccess) {
    return err;
  }

  // Align the user ptr to HW requirments.
  resDesc.res.linear.devPtr =
      static_cast<char*>(const_cast<void*>(devPtr)) - (offset != nullptr ? *offset : 0);
  hipTextureDesc texDesc = hip::getTextureDesc(texref);

  return ihipCreateTextureObject(const_cast<hipTextureObject_t*>(&texref->textureObject), &resDesc,
                                 &texDesc, nullptr);
}

hipError_t ihipBindTexture2D(size_t* offset, const textureReference* texref, const void* devPtr,
                             const hipChannelFormatDesc* desc, size_t width, size_t height,
                             size_t pitch) {
  if ((texref == nullptr) || (devPtr == nullptr) || (desc == nullptr) || (pitch == 0)) {
    return hipErrorInvalidValue;
  }

  // Any previous address or HIP array state associated with the texture reference is superseded by
  // this function. Any memory previously bound to hTexRef is unbound. No need to check for errors.
  hipError_t err = ihipDestroyTextureObject(texref->textureObject);
  if (err != hipSuccess) {
    return err;
  }

  hipResourceDesc resDesc = {};
  resDesc.resType = hipResourceTypePitch2D;
  resDesc.res.pitch2D.devPtr = const_cast<void*>(devPtr);
  resDesc.res.pitch2D.desc = *desc;
  resDesc.res.pitch2D.width = width;
  resDesc.res.pitch2D.height = height;
  resDesc.res.pitch2D.pitchInBytes = pitch;
  err = ihipGetTextureAlignmentOffset(offset, devPtr);
  if (err != hipSuccess) {
    return err;
  }

  // Align the user ptr to HW requirments.
  resDesc.res.pitch2D.devPtr =
      static_cast<char*>(const_cast<void*>(devPtr)) - (offset != nullptr ? *offset : 0);
  hipTextureDesc texDesc = hip::getTextureDesc(texref);

  return ihipCreateTextureObject(const_cast<hipTextureObject_t*>(&texref->textureObject), &resDesc,
                                 &texDesc, nullptr);
}

hipError_t hipBindTexture2D(size_t* offset, const textureReference* texref, const void* devPtr,
                            const hipChannelFormatDesc* desc, size_t width, size_t height,
                            size_t pitch) {
  HIP_INIT_API(hipBindTexture2D, offset, texref, devPtr, desc, width, height, pitch);

  if (texref == nullptr) {
    HIP_RETURN(hipErrorUnknown);
  }
  if (devPtr == nullptr) {
    HIP_RETURN(hipErrorNotFound);
  }

  hipDeviceptr_t refDevPtr = nullptr;
  size_t refDevSize = 0;

  HIP_RETURN_ONFAIL(
      PlatformState::Instance().StatCO().GetGlobalVar(
          texref, ihipGetDevice(), &refDevPtr, &refDevSize));

  assert(refDevSize == sizeof(textureReference));
  hipError_t err = ihipBindTexture2D(offset, texref, devPtr, desc, width, height, pitch);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }
  // Copy to device.
  hip::Stream* stream = hip::getNullStream();
  HIP_RETURN(ihipMemcpy(refDevPtr, texref, refDevSize, hipMemcpyHostToDevice, *stream));
}

hipError_t ihipBindTextureToArray(const textureReference* texref, hipArray_const_t array,
                                  const hipChannelFormatDesc* desc) {
  // Any previous address or HIP array state associated with the texture reference is superseded by
  // this function. Any memory previously bound to hTexRef is unbound. No need to check for errors.
  hipError_t err = ihipDestroyTextureObject(texref->textureObject);
  if (err != hipSuccess) {
    return err;
  }

  hipResourceDesc resDesc = {};
  resDesc.resType = hipResourceTypeArray;
  resDesc.res.array.array = const_cast<hipArray_t>(array);

  hipTextureDesc texDesc = hip::getTextureDesc(texref);

  hipResourceViewFormat format = hip::getResourceViewFormat(*desc);
  hipResourceViewDesc resViewDesc = hip::getResourceViewDesc(array, format);

  return ihipCreateTextureObject(const_cast<hipTextureObject_t*>(&texref->textureObject), &resDesc,
                                 &texDesc, &resViewDesc);
}

hipError_t hipBindTextureToArray(const textureReference* texref, hipArray_const_t array,
                                 const hipChannelFormatDesc* desc) {
  HIP_INIT_API(hipBindTextureToArray, texref, array, desc);

  if ((texref == nullptr) || (array == nullptr) || (desc == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }

  hipDeviceptr_t refDevPtr = nullptr;
  size_t refDevSize = 0;
  HIP_RETURN_ONFAIL(
      PlatformState::Instance().StatCO().GetGlobalVar(
          texref, ihipGetDevice(), &refDevPtr, &refDevSize));

  assert(refDevSize == sizeof(textureReference));
  hipError_t err = ihipBindTextureToArray(texref, array, desc);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }
  // Copy to device.
  hip::Stream* stream = hip::getNullStream();
  HIP_RETURN(ihipMemcpy(refDevPtr, texref, refDevSize, hipMemcpyHostToDevice, *stream));
}

hipError_t ihipBindTextureToMipmappedArray(const textureReference* texref,
                                           hipMipmappedArray_const_t mipmappedArray,
                                           const hipChannelFormatDesc* desc) {
  if ((texref == nullptr) || (mipmappedArray == nullptr) || (desc == nullptr)) {
    return hipErrorInvalidValue;
  }

  // Any previous address or HIP array state associated with the texture reference is superseded by
  // this function. Any memory previously bound to hTexRef is unbound. No need to check for errors.
  hipError_t err = ihipDestroyTextureObject(texref->textureObject);
  if (err != hipSuccess) {
    return err;
  }

  hipResourceDesc resDesc = {};
  resDesc.resType = hipResourceTypeMipmappedArray;
  resDesc.res.mipmap.mipmap = const_cast<hipMipmappedArray_t>(mipmappedArray);

  hipTextureDesc texDesc = hip::getTextureDesc(texref);

  hipResourceViewFormat format = hip::getResourceViewFormat(*desc);
  hipResourceViewDesc resViewDesc = hip::getResourceViewDesc(mipmappedArray, format);

  return ihipCreateTextureObject(const_cast<hipTextureObject_t*>(&texref->textureObject), &resDesc,
                                 &texDesc, &resViewDesc);
}

hipError_t hipBindTextureToMipmappedArray(const textureReference* texref,
                                          hipMipmappedArray_const_t mipmappedArray,
                                          const hipChannelFormatDesc* desc) {
  HIP_INIT_API(hipBindTextureToMipmappedArray, texref, mipmappedArray, desc);

  hipDeviceptr_t refDevPtr = nullptr;
  size_t refDevSize = 0;

  HIP_RETURN_ONFAIL(
      PlatformState::Instance().StatCO().GetGlobalVar(
          texref, ihipGetDevice(), &refDevPtr, &refDevSize));

  assert(refDevSize == sizeof(textureReference));
  hipError_t err = ihipBindTextureToMipmappedArray(texref, mipmappedArray, desc);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }
  // Copy to device.
  hip::Stream* stream = hip::getNullStream();
  HIP_RETURN(ihipMemcpy(refDevPtr, texref, refDevSize, hipMemcpyHostToDevice, *stream));
}

hipError_t hipUnbindTexture(const textureReference* texref) {
  HIP_INIT_API(hipUnbindTexture, texref);

  HIP_RETURN(ihipUnbindTexture(const_cast<textureReference*>(texref)));
}

hipError_t hipBindTexture(size_t* offset, const textureReference* texref, const void* devPtr,
                          const hipChannelFormatDesc* desc, size_t size) {
  HIP_INIT_API(hipBindTexture, offset, texref, devPtr, desc, size);

  hipDeviceptr_t refDevPtr = nullptr;
  size_t refDevSize = 0;
  HIP_RETURN_ONFAIL(
      PlatformState::Instance().StatCO().GetGlobalVar(
          texref, ihipGetDevice(), &refDevPtr, &refDevSize));
  assert(refDevSize == sizeof(textureReference));
  hipError_t err = ihipBindTexture(offset, texref, devPtr, desc, size);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }
  // Copy to device.
  hip::Stream* stream = hip::getNullStream();
  HIP_RETURN(ihipMemcpy(refDevPtr, texref, refDevSize, hipMemcpyHostToDevice, *stream));
}

hipError_t hipGetChannelDesc(hipChannelFormatDesc* desc, hipArray_const_t array) {
  HIP_INIT_API(hipGetChannelDesc, desc, array);

  if (desc == nullptr) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  if (array == nullptr) {
    HIP_RETURN(hipErrorInvalidHandle);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  // It is UB to call hipGetChannelDesc() on an array created via
  // hipArrayCreate()/hipArray3DCreate(). This is due to hip not differentiating between runtime and
  // driver types.
  *desc = array->desc;

  HIP_RETURN(hipSuccess);
}

hipError_t hipGetTextureAlignmentOffset(size_t* offset, const textureReference* texref) {
  HIP_INIT_API(hipGetTextureAlignmentOffset, offset, texref);

  if (texref == nullptr) {
    HIP_RETURN(hipErrorInvalidTexture);
  }

  if (offset == nullptr) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  // TODO enforce alignment on devPtr.
  *offset = 0;

  HIP_RETURN(hipSuccess);
}

hipError_t hipGetTextureReference(const textureReference** texref, const void* symbol) {
  HIP_INIT_API(hipGetTextureReference, texref, symbol);

  if (texref == nullptr) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *texref = reinterpret_cast<const textureReference*>(symbol);

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefSetFormat(textureReference* texRef, hipArray_Format fmt,
                              int NumPackedComponents) {
  HIP_INIT_API(hipTexRefSetFormat, texRef, fmt, NumPackedComponents);

  if (texRef == nullptr) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  texRef->format = fmt;
  texRef->numChannels = NumPackedComponents;

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefSetFlags(textureReference* texRef, unsigned int Flags) {
  HIP_INIT_API(hipTexRefSetFlags, texRef, Flags);

  if (texRef == nullptr) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  texRef->readMode = hipReadModeNormalizedFloat;
  texRef->normalized = 0;
  texRef->sRGB = 0;

  if (Flags & HIP_TRSF_READ_AS_INTEGER) {
    texRef->readMode = hipReadModeElementType;
  }

  if (Flags & HIP_TRSF_NORMALIZED_COORDINATES) {
    texRef->normalized = 1;
  }

  if (Flags & HIP_TRSF_SRGB) {
    texRef->sRGB = 1;
  }

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefSetFilterMode(textureReference* texRef, hipTextureFilterMode fm) {
  HIP_INIT_API(hipTexRefSetFilterMode, texRef, fm);

  if (texRef == nullptr) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  texRef->filterMode = fm;

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefGetAddressMode(hipTextureAddressMode* pam, const textureReference* texRef,
                                   int dim) {
  // TODO overload operator<<(ostream&, textureReference&).
  HIP_INIT_API(hipTexRefGetAddressMode, pam, texRef, dim);

  if ((pam == nullptr) || (texRef == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  // Currently, the only valid value for dim are 0 and 1.
  if ((dim != 0) && (dim != 1)) {
    LogPrintfError(
        "Currently only 2 dimensions (0,1) are valid,"
        "dim : %d",
        dim);
    HIP_RETURN(hipErrorInvalidValue);
  }

  *pam = texRef->addressMode[dim];

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefSetAddressMode(textureReference* texRef, int dim, hipTextureAddressMode am) {
  HIP_INIT_API(hipTexRefSetAddressMode, texRef, dim, am);

  if (texRef == nullptr) {
    HIP_RETURN(hipErrorInvalidValue);
  }

  if ((dim < 0) || (dim > 2)) {
    LogPrintfError(
        "Currently only 3 dimensions (0,1,2) are valid,"
        "dim : %d",
        dim);
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  texRef->addressMode[dim] = am;

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefGetArray(hipArray_t* pArray, const textureReference* texRef) {
  // TODO overload operator<<(ostream&, textureReference&).
  HIP_INIT_API(hipTexRefGetArray, pArray, texRef);

  if ((pArray == nullptr) || (texRef == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }

  hipResourceDesc resDesc = {};
  // TODO use ihipGetTextureObjectResourceDesc() to not pollute the API trace.
  hipError_t error = ihipGetTextureObjectResourceDesc(&resDesc, texRef->textureObject);
  if (error != hipSuccess) {
    HIP_RETURN(error);
  }

  switch (resDesc.resType) {
    case hipResourceTypeLinear:
    case hipResourceTypePitch2D:
    case hipResourceTypeMipmappedArray: {
      HIP_RETURN(hipErrorInvalidValue);
    }
    case hipResourceTypeArray:
      *pArray = resDesc.res.array.array;
      break;
  }

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefSetArray(textureReference* texRef, hipArray_const_t array, unsigned int flags) {
  HIP_INIT_API(hipTexRefSetArray, texRef, array, flags);

  if ((texRef == nullptr) || (array == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }

  if (flags != HIP_TRSA_OVERRIDE_FORMAT) {
    HIP_RETURN(hipErrorInvalidValue);
  }

  hipDeviceptr_t refDevPtr = nullptr;
  size_t refDevSize = 0;

  HIP_RETURN_ONFAIL(PlatformState::Instance().GetDynTexGlobalVar(texRef, &refDevPtr, &refDevSize));
  assert(refDevSize == sizeof(textureReference));

  // Any previous address or HIP array state associated with the texture reference is superseded by
  // this function. Any memory previously bound to hTexRef is unbound. No need to check for errors.
  hipError_t err = ihipDestroyTextureObject(texRef->textureObject);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }

  // If flag = HIP_TRSA_OVERRIDE_FORMAT,
  // Override the texref format with a format inferred from the array
  texRef->format = array->Format;
  texRef->numChannels = array->NumChannels;

  hipResourceDesc resDesc = {};
  resDesc.resType = hipResourceTypeArray;
  resDesc.res.array.array = const_cast<hipArray_t>(array);

  hipTextureDesc texDesc = hip::getTextureDesc(texRef);

  hipResourceViewFormat format =
      hip::getResourceViewFormat(hip::getChannelFormatDesc(texRef->numChannels, texRef->format));
  hipResourceViewDesc resViewDesc = hip::getResourceViewDesc(array, format);

  err = ihipCreateTextureObject(&texRef->textureObject, &resDesc, &texDesc, &resViewDesc);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }
  // Copy to device.
  hip::Stream* stream = hip::getNullStream();
  HIP_RETURN(ihipMemcpy(refDevPtr, texRef, refDevSize, hipMemcpyHostToDevice, *stream));
}

hipError_t hipTexRefGetAddress(hipDeviceptr_t* dptr, const textureReference* texRef) {
  // TODO overload operator<<(ostream&, textureReference&).
  HIP_INIT_API(hipTexRefGetAddress, dptr, texRef);

  if ((dptr == nullptr) || (texRef == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }

  hipResourceDesc resDesc = {};
  // TODO use ihipGetTextureObjectResourceDesc() to not pollute the API trace.
  hipError_t error = ihipGetTextureObjectResourceDesc(&resDesc, texRef->textureObject);
  if (error != hipSuccess) {
    LogPrintfError("hipGetTextureObjectResourceDesc failed with error code: %s",
                   ihipGetErrorName(error));
    HIP_RETURN(error);
  }

  switch (resDesc.resType) {
    // Need to verify.
    // If the texture reference is not bound to any device memory range,
    // return hipErroInvalidValue.
    case hipResourceTypeArray:
    case hipResourceTypeMipmappedArray: {
      HIP_RETURN(hipErrorInvalidValue);
    }
    case hipResourceTypeLinear:
      *dptr = resDesc.res.linear.devPtr;
      break;
    case hipResourceTypePitch2D:
      *dptr = resDesc.res.pitch2D.devPtr;
      break;
  }

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefSetAddress(size_t* ByteOffset, textureReference* texRef, hipDeviceptr_t dptr,
                               size_t bytes) {
  HIP_INIT_API(hipTexRefSetAddress, ByteOffset, texRef, dptr, bytes);

  if (texRef == nullptr) {
    HIP_RETURN(hipErrorInvalidValue);
  }

  hipDeviceptr_t refDevPtr = nullptr;
  size_t refDevSize = 0;
  HIP_RETURN_ONFAIL(PlatformState::Instance().GetDynTexGlobalVar(texRef, &refDevPtr, &refDevSize));
  assert(refDevSize == sizeof(textureReference));

  // Any previous address or HIP array state associated with the texture reference is superseded by
  // this function. Any memory previously bound to hTexRef is unbound. No need to check for errors.
  hipError_t err = ihipDestroyTextureObject(texRef->textureObject);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }

  hipResourceDesc resDesc = {};
  resDesc.resType = hipResourceTypeLinear;
  resDesc.res.linear.devPtr = dptr;
  resDesc.res.linear.desc = hip::getChannelFormatDesc(texRef->numChannels, texRef->format);
  resDesc.res.linear.sizeInBytes = bytes;
  err = ihipGetTextureAlignmentOffset(ByteOffset, dptr);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }

  // Align the user ptr to HW requirments.
  resDesc.res.linear.devPtr = static_cast<char*>(dptr) - (ByteOffset ? *ByteOffset : 0);
  hipTextureDesc texDesc = hip::getTextureDesc(texRef);

  err = ihipCreateTextureObject(&texRef->textureObject, &resDesc, &texDesc, nullptr);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }
  // Copy to device.
  hip::Stream* stream = hip::getNullStream();
  HIP_RETURN(ihipMemcpy(refDevPtr, texRef, refDevSize, hipMemcpyHostToDevice, *stream));
}

hipError_t hipTexRefSetAddress2D(textureReference* texRef, const HIP_ARRAY_DESCRIPTOR* desc,
                                 hipDeviceptr_t dptr, size_t Pitch) {
  HIP_INIT_API(hipTexRefSetAddress2D, texRef, desc, dptr, Pitch);

  if ((texRef == nullptr) || (desc == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }

  hipDeviceptr_t refDevPtr = nullptr;
  size_t refDevSize = 0;
  HIP_RETURN_ONFAIL(PlatformState::Instance().GetDynTexGlobalVar(texRef, &refDevPtr, &refDevSize));
  assert(refDevSize == sizeof(textureReference));

  // Any previous address or HIP array state associated with the texture reference is superseded by
  // this function. Any memory previously bound to hTexRef is unbound. No need to check for errors.
  hipError_t err = ihipDestroyTextureObject(texRef->textureObject);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }

  hipResourceDesc resDesc = {};
  resDesc.resType = hipResourceTypePitch2D;
  resDesc.res.linear.devPtr = dptr;
  resDesc.res.linear.desc =
      hip::getChannelFormatDesc(desc->NumChannels, desc->Format);  // Need to verify.
  resDesc.res.pitch2D.width = desc->Width;
  resDesc.res.pitch2D.height = desc->Height;
  resDesc.res.pitch2D.pitchInBytes = Pitch;

  hipTextureDesc texDesc = hip::getTextureDesc(texRef);

  err = ihipCreateTextureObject(&texRef->textureObject, &resDesc, &texDesc, nullptr);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }
  // Copy to device.
  hip::Stream* stream = hip::getNullStream();
  HIP_RETURN(ihipMemcpy(refDevPtr, texRef, refDevSize, hipMemcpyHostToDevice, *stream));
}

hipChannelFormatDesc hipCreateChannelDesc(int x, int y, int z, int w, hipChannelFormatKind f) {
  return {x, y, z, w, f};
}

hipError_t hipTexRefGetBorderColor(float* pBorderColor, const textureReference* texRef) {
  // TODO overload operator<<(ostream&, textureReference&).
  HIP_INIT_API(hipTexRefGetBorderColor, pBorderColor, texRef);

  if ((pBorderColor == nullptr) || (texRef == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  // TODO add textureReference::borderColor.
  assert(false && "textureReference::borderColor is missing in header");
  // std::memcpy(pBorderColor, texRef.borderColor, sizeof(texRef.borderColor));

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefGetFilterMode(hipTextureFilterMode* pfm, const textureReference* texRef) {
  // TODO overload operator<<(ostream&, textureReference&).
  HIP_INIT_API(hipTexRefGetFilterMode, pfm, texRef);

  if ((pfm == nullptr) || (texRef == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *pfm = texRef->filterMode;

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefGetFlags(unsigned int* pFlags, const textureReference* texRef) {
  // TODO overload operator<<(ostream&, textureReference&).
  HIP_INIT_API(hipTexRefGetFlags, pFlags, texRef);

  if ((pFlags == nullptr) || (texRef == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *pFlags = 0;

  if (texRef->readMode == hipReadModeElementType) {
    *pFlags |= HIP_TRSF_READ_AS_INTEGER;
  }

  if (texRef->normalized == 1) {
    *pFlags |= HIP_TRSF_NORMALIZED_COORDINATES;
  }

  if (texRef->sRGB == 1) {
    *pFlags |= HIP_TRSF_SRGB;
  }

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefGetFormat(hipArray_Format* pFormat, int* pNumChannels,
                              const textureReference* texRef) {
  // TODO overload operator<<(ostream&, textureReference&).
  HIP_INIT_API(hipTexRefGetFormat, pFormat, pNumChannels, texRef);

  if ((pFormat == nullptr) || (pNumChannels == nullptr) || (texRef == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *pFormat = texRef->format;
  *pNumChannels = texRef->numChannels;

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefGetMaxAnisotropy(int* pmaxAnsio, const textureReference* texRef) {
  // TODO overload operator<<(ostream&, textureReference&).
  HIP_INIT_API(hipTexRefGetMaxAnisotropy, pmaxAnsio, texRef);

  if ((pmaxAnsio == nullptr) || (texRef == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *pmaxAnsio = texRef->maxAnisotropy;

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefGetMipmapFilterMode(hipTextureFilterMode* pfm, const textureReference* texRef) {
  // TODO overload operator<<(ostream&, textureReference&).
  HIP_INIT_API(hipTexRefGetMipmapFilterMode, pfm, texRef);

  if ((pfm == nullptr) || (texRef == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *pfm = texRef->mipmapFilterMode;

  HIP_RETURN(hipErrorInvalidValue);
}

hipError_t hipTexRefGetMipmapLevelBias(float* pbias, const textureReference* texRef) {
  // TODO overload operator<<(ostream&, textureReference&).
  HIP_INIT_API(hipTexRefGetMipmapLevelBias, pbias, texRef);

  if ((pbias == nullptr) || (texRef == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *pbias = texRef->mipmapLevelBias;

  HIP_RETURN(hipErrorInvalidValue);
}

hipError_t hipTexRefGetMipmapLevelClamp(float* pminMipmapLevelClamp, float* pmaxMipmapLevelClamp,
                                        const textureReference* texRef) {
  // TODO overload operator<<(ostream&, textureReference&).
  HIP_INIT_API(hipTexRefGetMipmapLevelClamp, pminMipmapLevelClamp, pmaxMipmapLevelClamp, texRef);

  if ((pminMipmapLevelClamp == nullptr) || (pmaxMipmapLevelClamp == nullptr) ||
      (texRef == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *pminMipmapLevelClamp = texRef->minMipmapLevelClamp;
  *pmaxMipmapLevelClamp = texRef->maxMipmapLevelClamp;

  HIP_RETURN(hipErrorInvalidValue);
}

hipError_t hipTexRefGetMipMappedArray(hipMipmappedArray_t* pArray, const textureReference* texRef) {
  // TODO overload operator<<(ostream&, textureReference&).
  HIP_INIT_API(hipTexRefGetMipMappedArray, pArray, &texRef);

  if ((pArray == nullptr) || (texRef == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  hipResourceDesc resDesc = {};
  // TODO use ihipGetTextureObjectResourceDesc() to not pollute the API trace.
  hipError_t error = ihipGetTextureObjectResourceDesc(&resDesc, texRef->textureObject);
  if (error != hipSuccess) {
    HIP_RETURN(error);
  }

  switch (resDesc.resType) {
    case hipResourceTypeLinear:
    case hipResourceTypePitch2D:
    case hipResourceTypeArray: {
      HIP_RETURN(hipErrorInvalidValue);
    }
    case hipResourceTypeMipmappedArray:
      *pArray = resDesc.res.mipmap.mipmap;
      break;
  }

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefSetBorderColor(textureReference* texRef, float* pBorderColor) {
  HIP_INIT_API(hipTexRefSetBorderColor, texRef, pBorderColor);

  if ((texRef == nullptr) || (pBorderColor == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  // TODO add textureReference::borderColor.
  assert(false && "textureReference::borderColor is missing in header");
  // std::memcpy(texRef.borderColor, pBorderColor, sizeof(texRef.borderColor));

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefSetMaxAnisotropy(textureReference* texRef, unsigned int maxAniso) {
  HIP_INIT_API(hipTexRefSetMaxAnisotropy, texRef, maxAniso);

  if (texRef == nullptr) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  texRef->maxAnisotropy = maxAniso;

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefSetMipmapFilterMode(textureReference* texRef, hipTextureFilterMode fm) {
  HIP_INIT_API(hipTexRefSetMipmapFilterMode, texRef, fm);

  if (texRef == nullptr) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  texRef->mipmapFilterMode = fm;

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefSetMipmapLevelBias(textureReference* texRef, float bias) {
  HIP_INIT_API(hipTexRefSetMipmapLevelBias, texRef, bias);

  if (texRef == nullptr) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  texRef->mipmapLevelBias = bias;

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefSetMipmapLevelClamp(textureReference* texRef, float minMipMapLevelClamp,
                                        float maxMipMapLevelClamp) {
  HIP_INIT_API(hipTexRefSetMipmapLevelClamp, minMipMapLevelClamp, maxMipMapLevelClamp);

  if (texRef == nullptr) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  texRef->minMipmapLevelClamp = minMipMapLevelClamp;
  texRef->maxMipmapLevelClamp = maxMipMapLevelClamp;

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexRefSetMipmappedArray(textureReference* texRef, hipMipmappedArray* mipmappedArray,
                                      unsigned int Flags) {
  HIP_INIT_API(hipTexRefSetMipmappedArray, texRef, mipmappedArray, Flags);

  if ((texRef == nullptr) || (mipmappedArray == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }

  if (Flags != HIP_TRSA_OVERRIDE_FORMAT) {
    HIP_RETURN(hipErrorInvalidValue);
  }

  hipDeviceptr_t refDevPtr = nullptr;
  size_t refDevSize = 0;
  HIP_RETURN_ONFAIL(PlatformState::Instance().GetDynTexGlobalVar(texRef, &refDevPtr, &refDevSize));
  assert(refDevSize == sizeof(textureReference));

  // Any previous address or HIP array state associated with the texture reference is superseded by
  // this function. Any memory previously bound to hTexRef is unbound. No need to check for errors.
  hipError_t err = ihipDestroyTextureObject(texRef->textureObject);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }

  hipResourceDesc resDesc = {};
  resDesc.resType = hipResourceTypeMipmappedArray;
  resDesc.res.mipmap.mipmap = mipmappedArray;

  hipTextureDesc texDesc = hip::getTextureDesc(texRef);
  texDesc.normalizedCoords = texRef->normalized;
  hipResourceViewFormat format =
      hip::getResourceViewFormat(hip::getChannelFormatDesc(texRef->numChannels, texRef->format));
  hipResourceViewDesc resViewDesc = hip::getResourceViewDesc(mipmappedArray, format);

  err = ihipCreateTextureObject(&texRef->textureObject, &resDesc, &texDesc, &resViewDesc);
  if (err != hipSuccess) {
    HIP_RETURN(err);
  }
  // Copy to device.
  hip::Stream* stream = hip::getNullStream();
  HIP_RETURN(ihipMemcpy(refDevPtr, texRef, refDevSize, hipMemcpyHostToDevice, *stream));
}

hipError_t hipTexObjectCreate(hipTextureObject_t* pTexObject, const HIP_RESOURCE_DESC* pResDesc,
                              const HIP_TEXTURE_DESC* pTexDesc,
                              const HIP_RESOURCE_VIEW_DESC* pResViewDesc) {
  HIP_INIT_API(hipTexObjectCreate, pTexObject, pResDesc, pTexDesc, pResViewDesc);

  if ((pTexObject == nullptr) || (pResDesc == nullptr) || (pTexDesc == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }

  if (static_cast<hipResourceType>(pResDesc->resType) == hipResourceTypePitch2D &&
      ((pResDesc->res.pitch2D.width == 0) || (pResDesc->res.pitch2D.height == 0))) {
    HIP_RETURN(hipSuccess);
  }

  hipResourceDesc resDesc = hip::getResourceDesc(*pResDesc);
  hipTextureDesc texDesc = hip::getTextureDesc(*pTexDesc);

  if (pResViewDesc != nullptr) {
    hipResourceViewDesc resViewDesc = hip::getResourceViewDesc(*pResViewDesc);
    HIP_RETURN(ihipCreateTextureObject(pTexObject, &resDesc, &texDesc, &resViewDesc));
  } else {
    HIP_RETURN(ihipCreateTextureObject(pTexObject, &resDesc, &texDesc, nullptr));
  }
}

hipError_t hipTexObjectDestroy(hipTextureObject_t texObject) {
  HIP_INIT_API(hipTexObjectDestroy, texObject);

  HIP_RETURN(ihipDestroyTextureObject(texObject));
}

hipError_t hipTexObjectGetResourceDesc(HIP_RESOURCE_DESC* pResDesc, hipTextureObject_t texObject) {
  HIP_INIT_API(hipTexObjectGetResourceDesc, pResDesc, texObject);

  if ((pResDesc == nullptr) || (texObject == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *pResDesc = hip::getResourceDesc(texObject->resDesc);

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexObjectGetResourceViewDesc(HIP_RESOURCE_VIEW_DESC* pResViewDesc,
                                           hipTextureObject_t texObject) {
  HIP_INIT_API(hipTexObjectGetResourceViewDesc, pResViewDesc, texObject);

  if ((pResViewDesc == nullptr) || (texObject == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *pResViewDesc = hip::getResourceViewDesc(texObject->resViewDesc);

  HIP_RETURN(hipSuccess);
}

hipError_t hipTexObjectGetTextureDesc(HIP_TEXTURE_DESC* pTexDesc, hipTextureObject_t texObject) {
  HIP_INIT_API(hipTexObjectGetTextureDesc, pTexDesc, texObject);

  if ((pTexDesc == nullptr) || (texObject == nullptr)) {
    HIP_RETURN(hipErrorInvalidValue);
  }
  amd::Device* device = hip::getCurrentDevice()->devices()[0];
  const device::Info& info = device->info();
  if (!info.imageSupport_) {
    LogPrintfError("Texture not supported on the device %s", info.name_);
    HIP_RETURN(hipErrorNotSupported);
  }

  *pTexDesc = hip::getTextureDesc(texObject->texDesc);

  HIP_RETURN(hipSuccess);
}
}  // namespace hip
