/*
 * Copyright (C) 2020-2026 Intel Corporation
 *
 * SPDX-License-Identifier: MIT
 *
 */

#include "shared/source/command_container/encode_surface_state.h"
#include "shared/source/device/device.h"
#include "shared/source/execution_environment/root_device_environment.h"
#include "shared/source/gmm_helper/gmm.h"
#include "shared/source/gmm_helper/resource_info.h"
#include "shared/source/helpers/basic_math.h"
#include "shared/source/helpers/bindless_heaps_helper.h"
#include "shared/source/helpers/gfx_core_helper.h"
#include "shared/source/helpers/surface_format_info.h"
#include "shared/source/image/image_surface_state.h"
#include "shared/source/memory_manager/allocation_properties.h"
#include "shared/source/memory_manager/memory_manager.h"
#include "shared/source/memory_manager/unified_memory_manager.h"
#include "shared/source/memory_manager/unified_memory_pooling.h"
#include "shared/source/utilities/arrayref.h"

#include "level_zero/core/source/device/device.h"
#include "level_zero/core/source/driver/driver_handle.h"
#include "level_zero/core/source/gfx_core_helpers/l0_gfx_core_helper.h"
#include "level_zero/core/source/helpers/properties_parser.h"
#include "level_zero/core/source/image/image_format_desc_helper.h"
#include "level_zero/core/source/image/image_formats.h"
#include "level_zero/core/source/image/image_hw.h"
#include "level_zero/core/source/image/image_imp.h"
#include "level_zero/core/source/sampler/sampler.h"

#include "encode_surface_state_args.h"

namespace L0 {

template <GFXCORE_FAMILY gfxCoreFamily>
ze_result_t ImageCoreFamily<gfxCoreFamily>::initialize(Device *device, const ze_image_desc_t *desc) {
    using RENDER_SURFACE_STATE = typename GfxFamily::RENDER_SURFACE_STATE;

    const auto &rootDeviceEnvironment = device->getNEODevice()->getRootDeviceEnvironment();
    StructuresLookupTable lookupTable = {};

    lookupTable.areImageProperties = true;
    lookupTable.imageProperties.imageDescriptor = convertDescriptor(*desc);

    auto parseResult = prepareL0StructuresLookupTable(lookupTable, desc->pNext);

    if (parseResult != ZE_RESULT_SUCCESS) {
        return parseResult;
    }

    bool isMediaFormatLayout = isMediaFormat(desc->format.layout);

    imgInfo.imgDesc = lookupTable.imageProperties.imageDescriptor;
    imgInfo.mipCount = imgInfo.imgDesc.numMipLevels > 1 ? imgInfo.imgDesc.numMipLevels : 0;

    if (lookupTable.isSrgb) {
        if (desc->format.layout != ZE_IMAGE_FORMAT_LAYOUT_8_8_8_8 || desc->format.type != ZE_IMAGE_FORMAT_TYPE_UNORM) {
            return ZE_RESULT_ERROR_UNSUPPORTED_IMAGE_FORMAT;
        }
        imgInfo.surfaceFormat = &ImageFormats::srgbFormatRGBA8;
        this->srgbImage = true;
    } else if (lookupTable.isDepthStencilFormat) {
        if (lookupTable.depthStencilFormat == ZE_DEPTH_STENCIL_FORMAT_D32_FLOAT_S8X24_UINT) {
            imgInfo.surfaceFormat = &ImageFormats::depthStencilFormatD32FS8;
        } else {
            imgInfo.surfaceFormat = &ImageFormats::depthStencilFormatD24S8;
        }
        this->depthStencilImage = true;
    } else {
        imgInfo.surfaceFormat = &ImageFormats::formats[desc->format.layout][desc->format.type];
    }
    imageFormatDesc = *const_cast<ze_image_desc_t *>(desc);

    UNRECOVERABLE_IF(device == nullptr);
    this->device = device;

    if (imgInfo.surfaceFormat->gmmSurfaceFormat == GMM_FORMAT_INVALID) {
        return ZE_RESULT_ERROR_UNSUPPORTED_IMAGE_FORMAT;
    }

    typename RENDER_SURFACE_STATE::SURFACE_TYPE surfaceType;
    switch (desc->type) {
    case ZE_IMAGE_TYPE_BUFFER:
    case ZE_IMAGE_TYPE_1D:
    case ZE_IMAGE_TYPE_1DARRAY:
        surfaceType = RENDER_SURFACE_STATE::SURFACE_TYPE_SURFTYPE_1D;
        break;
    case ZE_IMAGE_TYPE_2D:
    case ZE_IMAGE_TYPE_2DARRAY:
        surfaceType = RENDER_SURFACE_STATE::SURFACE_TYPE_SURFTYPE_2D;
        break;
    case ZE_IMAGE_TYPE_3D:
        surfaceType = RENDER_SURFACE_STATE::SURFACE_TYPE_SURFTYPE_3D;
        break;
    default:
        return ZE_RESULT_ERROR_INVALID_ARGUMENT;
    }

    imgInfo.linearStorage = surfaceType == RENDER_SURFACE_STATE::SURFACE_TYPE_SURFTYPE_1D;
    imgInfo.plane = lookupTable.imageProperties.isPlanarExtension ? static_cast<NEO::ImagePlane>(lookupTable.imageProperties.planeIndex + 1u) : NEO::ImagePlane::noPlane;
    imgInfo.useLocalMemory = false;

    if (lookupTable.bindlessImage && this->device->getNEODevice()->getBindlessHeapsHelper() == nullptr) {
        return ZE_RESULT_ERROR_UNSUPPORTED_FEATURE;
    }
    this->bindlessImage = lookupTable.bindlessImage;

    if (lookupTable.imageProperties.pitchedPtr) {
        if (isImageView() || (!this->bindlessImage && desc->type != ZE_IMAGE_TYPE_BUFFER && desc->type != ZE_IMAGE_TYPE_2D)) {
            return ZE_RESULT_ERROR_INVALID_ARGUMENT;
        }
        this->imageFromBuffer = true;
    } else if (desc->type == ZE_IMAGE_TYPE_BUFFER) {
        return ZE_RESULT_ERROR_INVALID_ARGUMENT;
    }

    if (lookupTable.sampledImage) {
        if (!this->bindlessImage || lookupTable.imageProperties.samplerDesc == nullptr) {
            return ZE_RESULT_ERROR_INVALID_ARGUMENT;
        }
        this->sampledImage = true;
        this->samplerDesc = *lookupTable.imageProperties.samplerDesc;
        this->samplerDesc.pNext = nullptr;
    }

    NEO::UsmMemAllocPool *usmPool = nullptr;

    if (!isImageView()) {
        if (lookupTable.isSharedHandle) {
            if (!lookupTable.sharedHandleType.isSupportedHandle) {
                return ZE_RESULT_ERROR_UNSUPPORTED_ENUMERATION;
            }
            if (lookupTable.sharedHandleType.isOpaqueFDHandle || lookupTable.sharedHandleType.isDMABUFHandle) {
                NEO::MemoryManager::OsHandleData osHandleData{static_cast<NEO::osHandle>(lookupTable.sharedHandleType.fd)};
                NEO::AllocationProperties properties(device->getRootDeviceIndex(), true, &imgInfo, NEO::AllocationType::sharedImage, device->getNEODevice()->getDeviceBitfield());
                allocation = device->getNEODevice()->getMemoryManager()->createGraphicsAllocationFromSharedHandle(osHandleData, properties, false, false, true, nullptr);
                device->getNEODevice()->getMemoryManager()->closeSharedHandle(allocation);
            } else if (lookupTable.sharedHandleType.isNTHandle) {
                NEO::MemoryManager::OsHandleData osHandleData{lookupTable.sharedHandleType.ntHandle};
                NEO::AllocationProperties properties(device->getRootDeviceIndex(), true, &imgInfo, NEO::AllocationType::sharedImage, device->getNEODevice()->getDeviceBitfield());
                allocation = device->getNEODevice()->getMemoryManager()->createGraphicsAllocationFromSharedHandle(osHandleData, properties, false, false, true, nullptr);
                if (allocation != nullptr && lookupTable.glTextureExt.present) {
                    this->device = device;
                    if (lookupTable.glTextureExt.numberOfSamples > 1) {
                        imgInfo.imgDesc.numSamples = lookupTable.glTextureExt.numberOfSamples;
                    }
                    this->applyGlTextureExtOverrides(allocation, imgInfo,
                                                     lookupTable.glTextureExt.pGmmResInfo,
                                                     lookupTable.glTextureExt.textureBufferOffset,
                                                     lookupTable.glTextureExt.glHWFormat,
                                                     lookupTable.glTextureExt.isAuxEnabled,
                                                     lookupTable.glTextureExt.numberOfSamples,
                                                     lookupTable.glTextureExt.mcsNtHandle,
                                                     lookupTable.glTextureExt.pGmmResInfoMcs,
                                                     lookupTable.glTextureExt.hasUnifiedMcsSurface);
                }
                if (allocation != nullptr) {
                    auto importedGmm = allocation->getDefaultGmm();
                    importedGmm->queryImageParams(imgInfo);
                }
            }
        } else {

            if (!this->imageFromBuffer) {
                NEO::AllocationProperties properties(device->getRootDeviceIndex(), true, &imgInfo, lookupTable.overrideAllocationType ? lookupTable.imageProperties.imageAllocationType : NEO::AllocationType::image, device->getNEODevice()->getDeviceBitfield());

                properties.flags.preferCompressed = isSuitableForCompression(lookupTable, imgInfo);

                allocation = device->getNEODevice()->getMemoryManager()->allocateGraphicsMemoryWithProperties(properties);
            } else {

                auto usmAllocation = this->device->getDriverHandle()->getSvmAllocsManager()->getSVMAlloc(lookupTable.imageProperties.pitchedPtr);
                if (usmAllocation == nullptr) {
                    return ZE_RESULT_ERROR_INVALID_ARGUMENT;
                }

                usmPool = this->device->getNEODevice()->getUsmPoolOwningPtr(lookupTable.imageProperties.pitchedPtr);
                if (usmPool && nullptr == usmPool->getPooledAllocationBasePtr(lookupTable.imageProperties.pitchedPtr)) {
                    return ZE_RESULT_ERROR_INVALID_ARGUMENT;
                }

                allocation = usmAllocation->gpuAllocations.getGraphicsAllocation(device->getRootDeviceIndex());
            }
        }
        if (allocation == nullptr) {
            return ZE_RESULT_ERROR_OUT_OF_DEVICE_MEMORY;
        }
    }

    if (this->imageFromBuffer) {
        imgInfo.linearStorage = true;
        if (desc->type == ZE_IMAGE_TYPE_BUFFER) {
            imgInfo.rowPitch = imgInfo.imgDesc.imageWidth * imgInfo.surfaceFormat->imageElementSizeInBytes;
        } else {
            imgInfo.imgDesc.imageRowPitch = getRowPitchFor2dImage(device, imgInfo);
            if (imgInfo.imgDesc.imageRowPitch > 0) {
                imgInfo.rowPitch = imgInfo.imgDesc.imageRowPitch;
            } else {
                imgInfo.rowPitch = imgInfo.imgDesc.imageWidth * imgInfo.surfaceFormat->imageElementSizeInBytes;
            }
        }
        imgInfo.slicePitch = imgInfo.rowPitch * imgInfo.imgDesc.imageHeight;
        imgInfo.qPitch = 0;
        if (!isImageView()) {
            imgInfo.size = imgInfo.slicePitch;
            imgInfo.offset = ptrDiff(lookupTable.imageProperties.pitchedPtr, allocation->getGpuAddress());
        }
    }

    implicitArgsSurfaceState = GfxFamily::cmdInitRenderSurfaceState;

    if (this->bindlessImage) {
        auto result = allocateBindlessSlot();
        if (result != ZE_RESULT_SUCCESS) {
            return result;
        }
        NEO::AllocationProperties imgImplicitArgsAllocProperties(device->getRootDeviceIndex(), NEO::ImageImplicitArgs::getSize(), NEO::AllocationType::buffer, device->getNEODevice()->getDeviceBitfield());
        implicitArgsAllocation = device->getNEODevice()->getMemoryManager()->allocateGraphicsMemoryWithProperties(imgImplicitArgsAllocProperties);
    } else if (this->device->getNEODevice()->getBindlessHeapsHelper()) {
        allocateImplicitArgsOnDemand();
    }

    auto gmm = this->allocation->getDefaultGmm();
    auto gmmHelper = static_cast<const NEO::RootDeviceEnvironment &>(rootDeviceEnvironment).getGmmHelper();

    if (!this->imageFromBuffer && gmm != nullptr) {
        NEO::ImagePlane yuvPlaneType = NEO::ImagePlane::noPlane;
        if (isImageView() && (sourceImageFormatDesc->format.layout == ZE_IMAGE_FORMAT_LAYOUT_NV12)) {
            yuvPlaneType = NEO::ImagePlane::planeY;
            if (imgInfo.plane == NEO::ImagePlane::planeU) {
                yuvPlaneType = NEO::ImagePlane::planeUV;
            }
        }
        gmm->updateImgInfoAndDesc(imgInfo, 0u, yuvPlaneType);

        if (lookupTable.isSharedHandle && lookupTable.glTextureExt.present) {
            imgInfo.offset = 0;
            imgInfo.xOffset = 0;
            imgInfo.yOffset = 0;
            imgInfo.yOffsetForUVPlane = 0;
        }

        if (imgInfo.plane == NEO::ImagePlane::noPlane) {
            imgInfo.xOffset = 0;
            imgInfo.yOffset = 0;
        }
    }

    if (lookupTable.imageProperties.customRowPitch > 0) {
        imgInfo.rowPitch = lookupTable.imageProperties.customRowPitch;
        imgInfo.imgDesc.imageRowPitch = lookupTable.imageProperties.customRowPitch;
        this->customRowPitch = lookupTable.imageProperties.customRowPitch;
    } else if (this->customRowPitch > 0) {
        imgInfo.rowPitch = this->customRowPitch;
        imgInfo.imgDesc.imageRowPitch = this->customRowPitch;
    }
    if (lookupTable.imageProperties.customSlicePitch > 0) {
        imgInfo.slicePitch = lookupTable.imageProperties.customSlicePitch;
        imgInfo.imgDesc.imageSlicePitch = lookupTable.imageProperties.customSlicePitch;
        this->customSlicePitch = lookupTable.imageProperties.customSlicePitch;
    } else if (this->customSlicePitch > 0) {
        imgInfo.slicePitch = this->customSlicePitch;
        imgInfo.imgDesc.imageSlicePitch = this->customSlicePitch;
    }

    imgInfo.print();

    NEO::SurfaceOffsets surfaceOffsets = {imgInfo.offset, imgInfo.xOffset, imgInfo.yOffset, imgInfo.yOffsetForUVPlane};

    const uint32_t cubeFaceIndex = (lookupTable.isSharedHandle && lookupTable.glTextureExt.present)
                                       ? lookupTable.glTextureExt.cubeFaceIndex
                                       : static_cast<uint32_t>(__GMM_NO_CUBE_MAP);

    const uint32_t numSamplesForSurfaceState = this->getNumSamples();
    auto programMultisampleSurfaceState = [&](RENDER_SURFACE_STATE *surfaceState) {
        using NUMBER_OF_MULTISAMPLES = typename RENDER_SURFACE_STATE::NUMBER_OF_MULTISAMPLES;

        if (numSamplesForSurfaceState <= 1) {
            surfaceState->setNumberOfMultisamples(NUMBER_OF_MULTISAMPLES::NUMBER_OF_MULTISAMPLES_MULTISAMPLECOUNT_1);
            return;
        }
        surfaceState->setNumberOfMultisamples(static_cast<NUMBER_OF_MULTISAMPLES>(this->getMcsMultisampleCount()));

        auto mcsAlloc = this->getMcsAllocation();
        const bool unifiedMcs = this->getIsUnifiedMcsSurface();
        if (mcsAlloc != nullptr || unifiedMcs) {
            auto mcsGmm = mcsAlloc ? mcsAlloc->getDefaultGmm() : gmm;
            auto *releaseHelper = device->getNEODevice()->getRootDeviceEnvironment().getReleaseHelper();
            NEO::EncodeSurfaceState<GfxFamily>::setAuxParamsForMCSCCS(surfaceState, releaseHelper);
            surfaceState->setAuxiliarySurfacePitch(mcsGmm->getUnifiedAuxPitchTiles());
            surfaceState->setAuxiliarySurfaceQPitch(mcsGmm->getAuxQPitch());
            NEO::EncodeSurfaceState<GfxFamily>::setClearColorParams(surfaceState, mcsGmm);
            NEO::ImageSurfaceStateHelper<GfxFamily>::setUnifiedAuxBaseAddress(surfaceState, mcsGmm);
        } else {
            using SURFACE_FORMAT = typename RENDER_SURFACE_STATE::SURFACE_FORMAT;
            const bool isDepthResource = this->depthStencilImage || (gmm && gmm->gmmResourceInfo->getResourceFlags()->Gpu.Depth);
            if (isDepthResource && surfaceState->getSurfaceFormat() != SURFACE_FORMAT::SURFACE_FORMAT_R32_FLOAT_X8X24_TYPELESS) {
                surfaceState->setMultisampledSurfaceStorageFormat(
                    RENDER_SURFACE_STATE::MULTISAMPLED_SURFACE_STORAGE_FORMAT::MULTISAMPLED_SURFACE_STORAGE_FORMAT_DEPTH_STENCIL);
            } else {
                surfaceState->setMultisampledSurfaceStorageFormat(
                    RENDER_SURFACE_STATE::MULTISAMPLED_SURFACE_STORAGE_FORMAT::MULTISAMPLED_SURFACE_STORAGE_FORMAT_MSS);
            }
        }
    };

    {
        surfaceState = GfxFamily::cmdInitRenderSurfaceState;
        packedSurfaceState = GfxFamily::cmdInitRenderSurfaceState;
        uint32_t minArrayElement, renderTargetViewExtent, depth;
        NEO::ImageSurfaceStateHelper<GfxFamily>::setImageSurfaceState(&surfaceState, imgInfo, gmm, *gmmHelper, cubeFaceIndex,
                                                                      this->allocation->getGpuAddress(), surfaceOffsets,
                                                                      isMediaFormatLayout, minArrayElement, renderTargetViewExtent);

        NEO::ImageSurfaceStateHelper<GfxFamily>::setImageSurfaceStateDimensions(&surfaceState, imgInfo, cubeFaceIndex, surfaceType, depth);
        surfaceState.setSurfaceMinLOD(0u);
        surfaceState.setMIPCountLOD(0u);
        NEO::ImageSurfaceStateHelper<GfxFamily>::setMipTailStartLOD(&surfaceState, gmm);

        if (!isMediaFormatLayout) {
            surfaceState.setShaderChannelSelectRed(
                static_cast<const typename RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT>(
                    shaderChannelSelect[desc->format.x]));
            surfaceState.setShaderChannelSelectGreen(
                static_cast<const typename RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT>(
                    shaderChannelSelect[desc->format.y]));
            surfaceState.setShaderChannelSelectBlue(
                static_cast<const typename RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT>(
                    shaderChannelSelect[desc->format.z]));
            surfaceState.setShaderChannelSelectAlpha(
                static_cast<const typename RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT>(
                    shaderChannelSelect[desc->format.w]));
        } else {
            surfaceState.setShaderChannelSelectRed(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_RED);
            surfaceState.setShaderChannelSelectGreen(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_GREEN);
            surfaceState.setShaderChannelSelectBlue(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_BLUE);
            surfaceState.setShaderChannelSelectAlpha(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_ZERO);
        }

        programMultisampleSurfaceState(&surfaceState);

        if (numSamplesForSurfaceState <= 1 && allocation->isCompressionEnabled()) {
            NEO::EncodeSurfaceState<GfxFamily>::setImageAuxParamsForCCS(&surfaceState, gmm);
        }

        if (gmm) {
            const bool isDepthResource = this->depthStencilImage || gmm->gmmResourceInfo->getResourceFlags()->Gpu.Depth;
            surfaceState.setDepthStencilResource(isDepthResource);
        }
    }

    if (this->bindlessImage) {
        auto ssInHeap = getBindlessSlot();
        copySurfaceStateToSSH(ssInHeap->ssPtr, 0u, NEO::BindlessImageSlot::image, false, 0u);

        if (this->sampledImage) {
            auto productFamily = this->device->getNEODevice()->getHardwareInfo().platform.eProductFamily;
            auto sampler = Sampler::create(productFamily, device, &this->samplerDesc);
            if (!sampler) {
                return ZE_RESULT_ERROR_OUT_OF_HOST_MEMORY;
            }
            auto &gfxCoreHelper = this->device->getGfxCoreHelper();
            auto surfaceStateSize = gfxCoreHelper.getRenderSurfaceStateSize();
            auto samplerStateOffset = static_cast<uint32_t>(NEO::BindlessImageSlot::sampler * surfaceStateSize);

            ArrayRef<uint8_t> ssInHeapSpan{reinterpret_cast<uint8_t *>(ssInHeap->ssPtr), ssInHeap->ssSize};
            sampler->copySamplerStateToDSH(ssInHeapSpan, samplerStateOffset);
            sampler->destroy();
        }
    }

    const auto &productHelper = rootDeviceEnvironment.getHelper<NEO::ProductHelper>();
    if (this->bindlessImage && implicitArgsAllocation) {
        NEO::ImageImplicitArgs imageImplicitArgs{};
        populateImageImplicitArgs(imageImplicitArgs);

        NEO::MemoryTransferHelper::transferMemoryToAllocation(productHelper.isBlitCopyRequiredForLocalMemory(rootDeviceEnvironment, *implicitArgsAllocation), *this->device->getNEODevice(), implicitArgsAllocation, 0u, &imageImplicitArgs, NEO::ImageImplicitArgs::getSize());
        this->encodeImplicitArgsSurfaceState();
        auto surfaceStateSize = this->device->getGfxCoreHelper().getRenderSurfaceStateSize();
        auto ssInHeap = getBindlessSlot();
        copySurfaceStateToSSH(ptrOffset(ssInHeap->ssPtr, surfaceStateSize), 0u, NEO::BindlessImageSlot::implicitArgs, false, 0u);
    }

    {

        NEO::ImageInfo imgInfoRedescirebed;
        [[maybe_unused]] uint32_t exponent;
        switch (imgInfo.surfaceFormat->imageElementSizeInBytes) {
        default:
            exponent = Math::log2(imgInfo.surfaceFormat->imageElementSizeInBytes);
            DEBUG_BREAK_IF(exponent >= 5u);
            imgInfoRedescirebed.surfaceFormat = &ImageFormats::surfaceFormatsForRedescribe[exponent % 5];
            break;
        case 3:
            imgInfoRedescirebed.surfaceFormat = &ImageFormats::surfaceFormatsForRedescribe[5];
            break;
        case 6:
            imgInfoRedescirebed.surfaceFormat = &ImageFormats::surfaceFormatsForRedescribe[6];
            break;
        }

        imgInfoRedescirebed.imgDesc = imgInfo.imgDesc;
        imgInfoRedescirebed.qPitch = imgInfo.qPitch;
        if (imgInfoRedescirebed.imgDesc.imageType == NEO::ImageType::image1DBuffer) {
            imgInfoRedescirebed.imgDesc.imageType = NEO::ImageType::image1D;
        }
        redescribedSurfaceState = GfxFamily::cmdInitRenderSurfaceState;

        uint32_t minArrayElement, renderTargetViewExtent, depth;
        NEO::ImageSurfaceStateHelper<GfxFamily>::setImageSurfaceState(&redescribedSurfaceState, imgInfoRedescirebed, gmm, *gmmHelper,
                                                                      cubeFaceIndex, this->allocation->getGpuAddress(), surfaceOffsets,
                                                                      desc->format.layout == ZE_IMAGE_FORMAT_LAYOUT_NV12, minArrayElement, renderTargetViewExtent);

        NEO::ImageSurfaceStateHelper<GfxFamily>::setImageSurfaceStateDimensions(&redescribedSurfaceState, imgInfoRedescirebed, cubeFaceIndex, surfaceType, depth);
        redescribedSurfaceState.setSurfaceMinLOD(0u);
        redescribedSurfaceState.setMIPCountLOD(0u);
        NEO::ImageSurfaceStateHelper<GfxFamily>::setMipTailStartLOD(&redescribedSurfaceState, gmm);

        if (imgInfoRedescirebed.surfaceFormat->gmmSurfaceFormat == GMM_FORMAT_R8_UINT_TYPE ||
            imgInfoRedescirebed.surfaceFormat->gmmSurfaceFormat == GMM_FORMAT_R16_UINT_TYPE ||
            imgInfoRedescirebed.surfaceFormat->gmmSurfaceFormat == GMM_FORMAT_R32_UINT_TYPE) {
            redescribedSurfaceState.setShaderChannelSelectRed(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_RED);
            redescribedSurfaceState.setShaderChannelSelectGreen(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_ZERO);
            redescribedSurfaceState.setShaderChannelSelectBlue(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_ZERO);
        } else if (imgInfoRedescirebed.surfaceFormat->gmmSurfaceFormat == GMM_FORMAT_R32G32_UINT_TYPE) {
            redescribedSurfaceState.setShaderChannelSelectRed(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_RED);
            redescribedSurfaceState.setShaderChannelSelectGreen(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_GREEN);
            redescribedSurfaceState.setShaderChannelSelectBlue(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_ZERO);
        } else {
            redescribedSurfaceState.setShaderChannelSelectRed(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_RED);
            redescribedSurfaceState.setShaderChannelSelectGreen(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_GREEN);
            redescribedSurfaceState.setShaderChannelSelectBlue(RENDER_SURFACE_STATE::SHADER_CHANNEL_SELECT_BLUE);
        }

        programMultisampleSurfaceState(&redescribedSurfaceState);

        if (numSamplesForSurfaceState <= 1 && allocation->isCompressionEnabled()) {
            NEO::EncodeSurfaceState<GfxFamily>::setImageAuxParamsForCCS(&redescribedSurfaceState, gmm);
        }

        if (gmm) {
            const bool isDepthResource = gmm->gmmResourceInfo->getResourceFlags()->Gpu.Depth;
            redescribedSurfaceState.setDepthStencilResource(isDepthResource);
        }
    }

    if (productHelper.isPackedCopyFormatSupported()) {
        packedSurfaceState = redescribedSurfaceState;

        NEO::EncodeSurfaceState<GfxFamily>::convertSurfaceStateToPacked(&packedSurfaceState, imgInfo);
    }

    return ZE_RESULT_SUCCESS;
}

template <GFXCORE_FAMILY gfxCoreFamily>
void ImageCoreFamily<gfxCoreFamily>::copySurfaceStateToSSH(void *surfaceStateHeap,
                                                           uint32_t surfaceStateOffset,
                                                           uint32_t bindlessSlot,
                                                           bool isMediaBlockArg,
                                                           uint32_t mipLevel) {
    using GfxFamily = typename NEO::GfxFamilyMapper<gfxCoreFamily>::GfxFamily;
    using RENDER_SURFACE_STATE = typename GfxFamily::RENDER_SURFACE_STATE;

    const RENDER_SURFACE_STATE *src = nullptr;

    switch (bindlessSlot) {
    case NEO::BindlessImageSlot::image:
        src = &surfaceState;
        break;
    case NEO::BindlessImageSlot::redescribedImage:
        src = &redescribedSurfaceState;
        break;
    case NEO::BindlessImageSlot::implicitArgs:
        src = &implicitArgsSurfaceState;
        break;
    case NEO::BindlessImageSlot::packedImage:
        src = &packedSurfaceState;
        break;
    default:
        UNRECOVERABLE_IF(true);
    }

    auto dst = ptrOffset(surfaceStateHeap, surfaceStateOffset);
    memcpy_s(dst, sizeof(RENDER_SURFACE_STATE), src, sizeof(RENDER_SURFACE_STATE));

    if (imgInfo.mipCount > 1u && (bindlessSlot == NEO::BindlessImageSlot::image ||
                                  bindlessSlot == NEO::BindlessImageSlot::redescribedImage ||
                                  bindlessSlot == NEO::BindlessImageSlot::packedImage)) {
        auto *dstRss = static_cast<RENDER_SURFACE_STATE *>(dst);
        const uint32_t mipCountLod = imgInfo.mipCount > 0u ? imgInfo.mipCount - 1u : 0u;
        const uint32_t clampedMip = std::min(mipLevel, mipCountLod);

        dstRss->setSurfaceMinLOD(clampedMip);
        dstRss->setMIPCountLOD(mipCountLod);

        if (this->allocation != nullptr) {
            auto *gmm = this->allocation->getDefaultGmm();
            if (gmm != nullptr) {
                NEO::ImageSurfaceStateHelper<GfxFamily>::setMipTailStartLOD(dstRss, gmm);
            }
        }
    }

    if (isMediaBlockArg) {
        RENDER_SURFACE_STATE *dstRss = static_cast<RENDER_SURFACE_STATE *>(dst);
        NEO::ImageSurfaceStateHelper<GfxFamily>::setWidthForMediaBlockSurfaceState(dstRss, imgInfo);
    }
}

template <GFXCORE_FAMILY gfxCoreFamily>
bool ImageCoreFamily<gfxCoreFamily>::isSuitableForCompression(const StructuresLookupTable &structuresLookupTable, const NEO::ImageInfo &imgInfo) {
    auto &hwInfo = device->getHwInfo();
    auto &loGfxCoreHelper = device->getNEODevice()->getRootDeviceEnvironment().getHelper<L0GfxCoreHelper>();

    if (structuresLookupTable.uncompressedHint) {
        return false;
    }

    return (loGfxCoreHelper.imageCompressionSupported(hwInfo) && !imgInfo.linearStorage);
}

template <GFXCORE_FAMILY gfxCoreFamily>
void ImageCoreFamily<gfxCoreFamily>::encodeImplicitArgsSurfaceState() {

    auto &helper = this->device->getGfxCoreHelper();
    auto gmmHelper = device->getNEODevice()->getGmmHelper();

    NEO::EncodeSurfaceStateArgs encodeArgs;
    encodeArgs.outMemory = &implicitArgsSurfaceState;
    encodeArgs.size = NEO::ImageImplicitArgs::getSize();
    encodeArgs.graphicsAddress = implicitArgsAllocation->getGpuAddress();
    encodeArgs.gmmHelper = gmmHelper;
    encodeArgs.allocation = implicitArgsAllocation;
    encodeArgs.numAvailableDevices = this->device->getNEODevice()->getNumGenericSubDevices();
    encodeArgs.areMultipleSubDevicesInContext = encodeArgs.numAvailableDevices > 1;
    encodeArgs.mocs = helper.getMocsIndex(*encodeArgs.gmmHelper, true, false) << 1;
    encodeArgs.implicitScaling = this->device->isImplicitScalingCapable();
    encodeArgs.isDebuggerActive = this->device->getNEODevice()->getDebugger() != nullptr;

    helper.encodeBufferSurfaceState(encodeArgs);
}
} // namespace L0
