/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "shader_manager.h"

#include <algorithm>
#include <cinttypes>
#include <cstring>

#include <base/containers/array_view.h>
#include <core/io/intf_file_manager.h>
#include <render/device/gpu_resource_desc.h>
#include <render/device/pipeline_layout_desc.h>
#include <render/namespace.h>

#include "device/device.h"
#include "device/gpu_program.h"
#include "device/gpu_program_util.h"
#include "device/gpu_resource_handle_util.h"
#include "device/shader_module.h"
#include "device/shader_pipeline_binder.h"
#include "loader/shader_loader.h"
#include "resource_handle_impl.h"
#include "util/log.h"

using namespace BASE_NS;
using namespace CORE_NS;

constexpr uint64_t IA_HASH_PRIMITIVE_TOPOLOGY_SHIFT = 1;

constexpr uint64_t RS_HASH_POLYGON_MODE_SHIFT = 4;
constexpr uint64_t RS_HASH_CULL_MODE_SHIFT = 8;
constexpr uint64_t RS_HASH_FRONT_FACE_SHIFT = 12;

constexpr uint64_t DSS_HASH_DEPTH_COMPARE_SHIFT = 4;

constexpr uint64_t HASH_RS_SHIFT = 0;
constexpr uint64_t HASH_DS_SHIFT = 32;
constexpr uint64_t HASH_IA_SHIFT = 56;

union FloatAsUint32 {
    float f;
    uint32_t ui;
};

template<>
uint64_t BASE_NS::hash(const RENDER_NS::GraphicsState::InputAssembly& inputAssembly)
{
    uint64_t hash = 0;
    hash |= static_cast<uint64_t>(inputAssembly.enablePrimitiveRestart);
    hash |= (static_cast<uint64_t>(inputAssembly.primitiveTopology) << IA_HASH_PRIMITIVE_TOPOLOGY_SHIFT);
    return hash;
}

template<>
uint64_t BASE_NS::hash(const RENDER_NS::GraphicsState::RasterizationState& state)
{
    uint64_t hash = 0;
    hash |= (static_cast<uint64_t>(state.enableRasterizerDiscard) << 2u) |
            (static_cast<uint64_t>(state.enableDepthBias) << 1u) | static_cast<uint64_t>(state.enableDepthClamp);
    hash |= (static_cast<uint64_t>(state.polygonMode) << RS_HASH_POLYGON_MODE_SHIFT);
    hash |= (static_cast<uint64_t>(state.cullModeFlags) << RS_HASH_CULL_MODE_SHIFT);
    hash |= (static_cast<uint64_t>(state.frontFace) << RS_HASH_FRONT_FACE_SHIFT);
    return hash;
}

template<>
uint64_t BASE_NS::hash(const RENDER_NS::GraphicsState::DepthStencilState& state)
{
    uint64_t hash = 0;
    hash |= (static_cast<uint64_t>(state.enableStencilTest) << 3u) |
            (static_cast<uint64_t>(state.enableDepthBoundsTest) << 2u) |
            (static_cast<uint64_t>(state.enableDepthWrite) << 1u) | static_cast<uint64_t>(state.enableDepthTest);
    hash |= (static_cast<uint64_t>(state.depthCompareOp) << DSS_HASH_DEPTH_COMPARE_SHIFT);
    return hash;
}

template<>
uint64_t BASE_NS::hash(const RENDER_NS::GraphicsState::ColorBlendState::Attachment& state)
{
    uint64_t hash = 0;
    hash |= (static_cast<uint64_t>(state.enableBlend) << 0u);
    // blend factor values 0 - 18, 0x1f for exact (5 bits)
    hash |= (static_cast<uint64_t>(state.srcColorBlendFactor) << 1u);
    hash |= ((static_cast<uint64_t>(state.dstColorBlendFactor) & 0x1f) << 6u);
    hash |= ((static_cast<uint64_t>(state.srcAlphaBlendFactor) & 0x1f) << 12u);
    hash |= ((static_cast<uint64_t>(state.dstAlphaBlendFactor) & 0x1f) << 18u);
    // blend op values 0 - 4, 0x7 for exact (3 bits)
    hash |= ((static_cast<uint64_t>(state.colorBlendOp) & 0x7) << 24u);
    hash |= ((static_cast<uint64_t>(state.alphaBlendOp) & 0x7) << 28u);
    return hash;
}

template<>
uint64_t BASE_NS::hash(const RENDER_NS::GraphicsState::ColorBlendState& state)
{
    uint64_t hash = 0;
    hash |= (static_cast<uint64_t>(state.enableLogicOp) << 0u);
    hash |= (static_cast<uint64_t>(state.logicOp) << 1u);

    FloatAsUint32 vec[4u] = { { state.colorBlendConstants[0u] }, { state.colorBlendConstants[1u] },
        { state.colorBlendConstants[2u] }, { state.colorBlendConstants[3u] } };
    const uint64_t hashRG = (static_cast<uint64_t>(vec[0u].ui) << 32) | (vec[1u].ui);
    const uint64_t hashBA = (static_cast<uint64_t>(vec[2u].ui) << 32) | (vec[3u].ui);
    HashCombine(hash, hashRG, hashBA);
    for (uint32_t idx = 0; idx < state.colorAttachmentCount; ++idx) {
        HashCombine(hash, state.colorAttachments[idx]);
    }
    return hash;
}

template<>
uint64_t BASE_NS::hash(const RENDER_NS::GraphicsState& state)
{
    const uint64_t iaHash = hash(state.inputAssembly);
    const uint64_t rsHash = hash(state.rasterizationState);
    const uint64_t dsHash = hash(state.depthStencilState);
    const uint64_t cbsHash = hash(state.colorBlendState);
    uint64_t finalHash = (iaHash << HASH_IA_SHIFT) | (rsHash << HASH_RS_SHIFT) | (dsHash << HASH_DS_SHIFT);
    HashCombine(finalHash, cbsHash);
    return finalHash;
}

RENDER_BEGIN_NAMESPACE()
namespace {
constexpr inline bool IsUniformBuffer(const DescriptorType descriptorType)
{
    return ((descriptorType == CORE_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) ||
            (descriptorType == CORE_DESCRIPTOR_TYPE_UNIFORM_BUFFER));
}
constexpr inline bool IsStorageBuffer(const DescriptorType descriptorType)
{
    return ((descriptorType == CORE_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC) ||
            (descriptorType == CORE_DESCRIPTOR_TYPE_STORAGE_BUFFER));
}

ShaderManager::CompatibilityFlags GetPipelineLayoutCompatibilityFlags(
    const PipelineLayout& lhs, const PipelineLayout& rhs)
{
    ShaderManager::CompatibilityFlags flags = ShaderManager::CompatibilityFlagBits::COMPATIBLE_BIT;
    for (uint32_t setIdx = 0; setIdx < PipelineLayoutConstants::MAX_DESCRIPTOR_SET_COUNT; ++setIdx) {
        const auto& lSet = lhs.descriptorSetLayouts[setIdx];
        const auto& rSet = rhs.descriptorSetLayouts[setIdx];
        if (lSet.set == rSet.set) {
            for (uint32_t lIdx = 0; lIdx < lSet.bindings.size(); ++lIdx) {
                const auto& lBind = lSet.bindings[lIdx];
                for (uint32_t rIdx = 0; rIdx < rSet.bindings.size(); ++rIdx) {
                    const auto& rBind = rSet.bindings[rIdx];
                    if (lBind.binding == rBind.binding) {
                        if ((lBind.descriptorCount != rBind.descriptorCount) ||
                            (lBind.descriptorType != rBind.descriptorType)) {
                            // re-check dynamic offsets
                            if ((IsUniformBuffer(lBind.descriptorType) != IsUniformBuffer(rBind.descriptorType)) &&
                                (IsStorageBuffer(lBind.descriptorType) != IsStorageBuffer(rBind.descriptorType))) {
                                flags = 0;
                            }
                        }
                    }
                }
            }
        }
    }
    if (flags != 0) {
        // check for exact match
        bool isExact = true;
        for (uint32_t setIdx = 0; setIdx < PipelineLayoutConstants::MAX_DESCRIPTOR_SET_COUNT; ++setIdx) {
            const auto& lSet = lhs.descriptorSetLayouts[setIdx];
            const auto& rSet = rhs.descriptorSetLayouts[setIdx];
            if (lSet.set == rSet.set) {
                if (lSet.bindings.size() == rSet.bindings.size()) {
                    for (size_t idx = 0; idx < lSet.bindings.size(); ++idx) {
                        const int cmpRes =
                            std::memcmp(&(lSet.bindings[idx]), &(rSet.bindings[idx]), sizeof(lSet.bindings[idx]));
                        if (cmpRes != 0) {
                            isExact = false;
                            break;
                        }
                    }
                } else {
                    isExact = false;
                    break;
                }
            }
        }
        if (isExact) {
            flags |= ShaderManager::CompatibilityFlagBits::EXACT_BIT;
        }
    }
    return flags;
}

// NOTE: checking the type for validity is enough
inline bool IsComputeShaderFunc(RenderHandle handle)
{
    return RenderHandleType::COMPUTE_SHADER_STATE_OBJECT == RenderHandleUtil::GetHandleType(handle);
}

inline bool IsShaderFunc(RenderHandle handle)
{
    return RenderHandleType::SHADER_STATE_OBJECT == RenderHandleUtil::GetHandleType(handle);
}

inline bool IsAnyShaderFunc(RenderHandle handle)
{
    return (RenderHandleType::COMPUTE_SHADER_STATE_OBJECT == RenderHandleUtil::GetHandleType(handle)) ||
           (RenderHandleType::SHADER_STATE_OBJECT == RenderHandleUtil::GetHandleType(handle));
}

inline void GetShadersBySlot(
    const uint32_t renderSlotId, const ShaderManager::ComputeMappings& mappings, vector<RenderHandleReference>& shaders)
{
    for (const auto& ref : mappings.clientData) {
        if (ref.renderSlotId == renderSlotId) {
            shaders.push_back(ref.rhr);
        }
    }
}

inline void GetShadersBySlot(const uint32_t renderSlotId, const ShaderManager::GraphicsMappings& mappings,
    vector<RenderHandleReference>& shaders)
{
    for (const auto& ref : mappings.clientData) {
        if (ref.renderSlotId == renderSlotId) {
            shaders.push_back(ref.rhr);
        }
    }
}

inline void GetShadersBySlot(
    const uint32_t renderSlotId, const ShaderManager::ComputeMappings& mappings, vector<RenderHandle>& shaders)
{
    for (const auto& ref : mappings.clientData) {
        if (ref.renderSlotId == renderSlotId) {
            shaders.push_back(ref.rhr.GetHandle());
        }
    }
}

inline void GetShadersBySlot(
    const uint32_t renderSlotId, const ShaderManager::GraphicsMappings& mappings, vector<RenderHandle>& shaders)
{
    for (const auto& ref : mappings.clientData) {
        if (ref.renderSlotId == renderSlotId) {
            shaders.push_back(ref.rhr.GetHandle());
        }
    }
}

inline void GetGraphicsStatesBySlot(
    const uint32_t renderSlotId, const ShaderManager::GraphicsStateData& gsd, vector<RenderHandleReference>& states)
{
    PLUGIN_ASSERT(gsd.data.size() == gsd.rhr.size());
    for (size_t idx = 0; idx < gsd.data.size(); ++idx) {
        const auto& ref = gsd.data[idx];
        if (ref.renderSlotId == renderSlotId) {
            states.push_back(gsd.rhr[idx]);
        }
    }
}

inline void GetGraphicsStatesBySlot(
    const uint32_t renderSlotId, const ShaderManager::GraphicsStateData& gsd, vector<RenderHandle>& states)
{
    PLUGIN_ASSERT(gsd.data.size() == gsd.rhr.size());
    for (size_t idx = 0; idx < gsd.data.size(); ++idx) {
        const auto& ref = gsd.data[idx];
        if (ref.renderSlotId == renderSlotId) {
            states.push_back(gsd.rhr[idx].GetHandle());
        }
    }
}

inline RenderHandle GetHandle(const string_view path, const unordered_map<string, RenderHandle>& nameToClientHandle)
{
    if (auto const pos = nameToClientHandle.find(path); pos != nameToClientHandle.end()) {
        return pos->second;
    }
    return {};
}

constexpr inline uint64_t HashHandleAndSlot(const RenderHandle& handle, const uint32_t renderSlotId)
{
    // normally there are < 16 render slot ids used which way less than 0xffff
    // NOTE: the render slot id might be an invalid index
    return (handle.id << 16ull) | (renderSlotId & 0xffff);
}

uint32_t GetBaseGraphicsStateVariantIndex(
    const ShaderManager::GraphicsStateData& graphicsStates, const ShaderManager::GraphicsStateVariantCreateInfo& vci)
{
    uint32_t baseVariantIndex = INVALID_SM_INDEX;
    if (!vci.baseShaderState.empty()) {
        const string fullBaseName = vci.baseShaderState + vci.baseVariant;
        if (const auto bhIter = graphicsStates.nameToIndex.find(fullBaseName);
            bhIter != graphicsStates.nameToIndex.cend()) {
            PLUGIN_ASSERT(bhIter->second < graphicsStates.rhr.size());
            if ((bhIter->second < graphicsStates.rhr.size()) && graphicsStates.rhr[bhIter->second]) {
                const RenderHandle baseHandle = graphicsStates.rhr[bhIter->second].GetHandle();
                baseVariantIndex = RenderHandleUtil::GetIndexPart(baseHandle);
            }
        } else {
            PLUGIN_LOG_W("base state not found (%s %s)", vci.baseShaderState.data(), vci.baseVariant.data());
        }
    }
    return baseVariantIndex;
}
} // namespace

ShaderManager::ShaderManager(Device& device) : device_(device) {}

ShaderManager::~ShaderManager() = default;

RenderHandleReference ShaderManager::Get(const RenderHandle& handle) const
{
    if (RenderHandleUtil::IsValid(handle)) {
        const RenderHandleType handleType = RenderHandleUtil::GetHandleType(handle);
        const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
        if (handleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) {
            if (arrayIndex < computeShaderMappings_.clientData.size()) {
                return computeShaderMappings_.clientData[arrayIndex].rhr;
            }
        } else if (handleType == RenderHandleType::SHADER_STATE_OBJECT) {
            if (arrayIndex < shaderMappings_.clientData.size()) {
                return shaderMappings_.clientData[arrayIndex].rhr;
            }
        } else if (handleType == RenderHandleType::GRAPHICS_STATE) {
            if (arrayIndex < graphicsStates_.rhr.size()) {
                return graphicsStates_.rhr[arrayIndex];
            }
        } else if (handleType == RenderHandleType::PIPELINE_LAYOUT) {
            if (arrayIndex < pl_.rhr.size()) {
                return pl_.rhr[arrayIndex];
            }
        }
        PLUGIN_LOG_I("invalid render handle (id: %" PRIu64 ", type: %u)", handle.id, static_cast<uint32_t>(handleType));
    }
    return RenderHandleReference {};
}

uint64_t ShaderManager::HashGraphicsState(const GraphicsState& graphicsState) const
{
    return BASE_NS::hash(graphicsState);
}

uint32_t ShaderManager::CreateRenderSlotId(const string_view renderSlot)
{
    if (renderSlot.empty()) {
        return INVALID_SM_INDEX;
    }

    if (const auto iter = renderSlotIds_.nameToId.find(renderSlot); iter != renderSlotIds_.nameToId.cend()) {
        return iter->second;
    } else { // create new id
        const uint32_t renderSlotId = static_cast<uint32_t>(renderSlotIds_.data.size());
        renderSlotIds_.nameToId[renderSlot] = renderSlotId;
        renderSlotIds_.data.push_back(RenderSlotData { renderSlotId, {}, {} });
        return renderSlotId;
    }
}

string ShaderManager::GetRenderSlotName(const uint32_t renderSlotId) const
{
    if (renderSlotId != INVALID_SM_INDEX) {
        for (const auto& ref : renderSlotIds_.nameToId) {
            if (ref.second == renderSlotId) {
                return ref.first;
            }
        }
    }
    return {};
}

string ShaderManager::GetCategoryName(const uint32_t categoryId) const
{
    if (categoryId != INVALID_SM_INDEX) {
        for (const auto& ref : category_.nameToId) {
            if (ref.second == categoryId) {
                return ref.first;
            }
        }
    }
    return {};
}

void ShaderManager::SetRenderSlotData(
    const uint32_t renderSlotId, const RenderHandleReference& shaderHandle, const RenderHandleReference& stateHandle)
{
    if (renderSlotId < static_cast<uint32_t>(renderSlotIds_.data.size())) {
#if (RENDER_VALIDATION_ENABLED == 1)
        string renderSlotName = GetRenderSlotName(renderSlotId);
#endif
        if (IsAnyShaderFunc(shaderHandle.GetHandle())) {
#if (RENDER_VALIDATION_ENABLED == 1)
            if (renderSlotIds_.data[renderSlotId].shader) {
                renderSlotName = GetRenderSlotName(renderSlotId);
                PLUGIN_LOG_W(
                    "RENDER_VALIDATION: Overwriting default shader for render slot (%s)", renderSlotName.c_str());
            }
#endif
            renderSlotIds_.data[renderSlotId].shader = shaderHandle;
        }
        if (RenderHandleUtil::GetHandleType(stateHandle.GetHandle()) == RenderHandleType::GRAPHICS_STATE) {
#if (RENDER_VALIDATION_ENABLED == 1)
            if (renderSlotIds_.data[renderSlotId].graphicsState) {
                renderSlotName = renderSlotName.empty() ? GetRenderSlotName(renderSlotId) : renderSlotName;
                PLUGIN_LOG_W(
                    "RENDER_VALIDATION: Overwriting default shader for render slot (%s)", renderSlotName.c_str());
            }
#endif
            renderSlotIds_.data[renderSlotId].graphicsState = stateHandle;
        }
    }
}

uint32_t ShaderManager::CreateCategoryId(const string_view name)
{
    if (name.empty()) {
        return INVALID_SM_INDEX;
    }

    if (const auto iter = category_.nameToId.find(name); iter != category_.nameToId.cend()) {
        return iter->second;
    } else { // create new id
        const uint32_t id = static_cast<uint32_t>(category_.data.size());
        category_.nameToId[name] = id;
        category_.data.push_back(string(name));
        return id;
    }
}

RenderHandle ShaderManager::CreateClientData(
    const string_view path, const RenderHandleType type, const ClientDataIndices& cdi)
{
    PLUGIN_ASSERT(computeShaderMappings_.clientData.size() == computeShaderMappings_.nameData.size());
    PLUGIN_ASSERT(shaderMappings_.clientData.size() == shaderMappings_.nameData.size());

    RenderHandle clientHandle;
    PLUGIN_ASSERT(
        (type == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) || (type == RenderHandleType::SHADER_STATE_OBJECT));
    const uint64_t frameIndex = device_.GetFrameCount();
    if (auto iter = nameToClientHandle_.find(path); iter != nameToClientHandle_.end()) {
        clientHandle = iter->second;
        // we update the frame index if the shader has been (re)loaded
        const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(clientHandle);
        if ((type == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) &&
            (arrayIndex < static_cast<uint32_t>(computeShaderMappings_.clientData.size()))) {
            computeShaderMappings_.clientData[arrayIndex].frameIndex = frameIndex;
        } else if (arrayIndex < static_cast<uint32_t>(shaderMappings_.clientData.size())) {
            shaderMappings_.clientData[arrayIndex].frameIndex = frameIndex;
        }
    } else {
        const uint32_t arrayIndex = (type == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT)
                                        ? static_cast<uint32_t>(computeShaderMappings_.clientData.size())
                                        : static_cast<uint32_t>(shaderMappings_.clientData.size());
        clientHandle = RenderHandleUtil::CreateGpuResourceHandle(type, 0, arrayIndex, 0);
        RenderHandleReference rhr =
            RenderHandleReference(clientHandle, IRenderReferenceCounter::Ptr(new ShaderReferenceCounter()));
        if (type == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) {
            computeShaderMappings_.clientData.push_back({ move(rhr), {}, cdi.renderSlotIndex, cdi.pipelineLayoutIndex,
                cdi.reflectionPipelineLayoutIndex, cdi.categoryIndex, frameIndex });
            computeShaderMappings_.nameData.push_back({});
        } else {
            shaderMappings_.clientData.push_back({ move(rhr), {}, cdi.renderSlotIndex, cdi.pipelineLayoutIndex,
                cdi.reflectionPipelineLayoutIndex, INVALID_SM_INDEX, INVALID_SM_INDEX, cdi.categoryIndex, frameIndex });
            shaderMappings_.nameData.push_back({});
        }
        if (!path.empty()) {
            nameToClientHandle_[path] = clientHandle;
        }
    }

    return clientHandle;
}

RenderHandleReference ShaderManager::Create(
    const ComputeShaderCreateData& createInfo, const ShaderPathCreateData& pathCreateInfo)
{
    PLUGIN_ASSERT(computeShaderMappings_.clientData.size() == computeShaderMappings_.nameData.size());

    const string fullName = createInfo.path + pathCreateInfo.variantName;
    // reflection pipeline layout
    uint32_t reflectionPlIndex = INVALID_SM_INDEX;
    if (const ShaderModule* cs = GetShaderModule(createInfo.shaderModuleIndex); cs) {
        const RenderHandleReference plRhr = CreatePipelineLayout({ fullName, cs->GetPipelineLayout() });
        reflectionPlIndex = RenderHandleUtil::GetIndexPart(plRhr.GetHandle());
    }

    auto const clientHandle = CreateClientData(fullName, RenderHandleType::COMPUTE_SHADER_STATE_OBJECT,
        { createInfo.renderSlotId, createInfo.pipelineLayoutIndex, reflectionPlIndex, createInfo.categoryId });
    if (createInfo.pipelineLayoutIndex != INVALID_SM_INDEX) {
        pl_.computeShaderToIndex[clientHandle] = createInfo.pipelineLayoutIndex;
    }

    {
        const auto lock = std::lock_guard(pendingMutex_);
        pendingAllocations_.computeShaders.push_back(
            { clientHandle, createInfo.shaderModuleIndex, createInfo.pipelineLayoutIndex });
    }
    if ((!createInfo.shaderFileStr.empty()) && RenderHandleUtil::IsValid(clientHandle)) {
        // update shader file always
        handleToShaderDataFile_.insert_or_assign(clientHandle, string(createInfo.shaderFileStr));
    }
    if (!createInfo.materialMetadata.empty()) {
        MaterialMetadata metadata { string(createInfo.materialMetadata), json::value {} };
        if (metadata.json = json::parse(metadata.raw.data()); metadata.json) {
            // update metadata always
            shaderToMetadata_.insert_or_assign(clientHandle, move(metadata));
        }
    }

    const uint32_t index = RenderHandleUtil::GetIndexPart(clientHandle);
    if (IsComputeShaderFunc(clientHandle) &&
        (index < static_cast<uint32_t>(computeShaderMappings_.clientData.size()))) {
        auto& nameDataRef = computeShaderMappings_.nameData[index];
        nameDataRef.path = createInfo.path;
        nameDataRef.variantName = pathCreateInfo.variantName;
        nameDataRef.displayName = pathCreateInfo.displayName;
        auto& clientDataRef = computeShaderMappings_.clientData[index];
        // add base shader if given
        if (!pathCreateInfo.baseShaderPath.empty()) {
            if (const auto baseHandleIter = nameToClientHandle_.find(pathCreateInfo.baseShaderPath);
                baseHandleIter != nameToClientHandle_.cend()) {
                if (RenderHandleUtil::IsValid(baseHandleIter->second)) {
                    clientDataRef.baseShaderHandle = baseHandleIter->second;
                    const uint64_t hash = HashHandleAndSlot(clientDataRef.baseShaderHandle, createInfo.renderSlotId);
                    hashToShaderVariant_[hash] = clientHandle;
                }
            } else {
                PLUGIN_LOG_W("base shader (%s) not found for (%s)", pathCreateInfo.baseShaderPath.data(),
                    createInfo.path.data());
            }
        }
        return clientDataRef.rhr;
    } else {
        return {};
    }
}

RenderHandleReference ShaderManager::Create(
    const ShaderCreateData& createInfo, const ShaderPathCreateData& pathCreateInfo)
{
    PLUGIN_ASSERT(shaderMappings_.clientData.size() == shaderMappings_.nameData.size());

    const string fullName = createInfo.path + pathCreateInfo.variantName;
    // reflection pipeline layout
    uint32_t reflectionPlIndex = INVALID_SM_INDEX;
    {
        const ShaderModule* vs = GetShaderModule(createInfo.vertShaderModuleIndex);
        const ShaderModule* fs = GetShaderModule(createInfo.fragShaderModuleIndex);
        if (vs && fs) {
            const PipelineLayout layouts[] { vs->GetPipelineLayout(), fs->GetPipelineLayout() };
            PipelineLayout pl;
            GpuProgramUtil::CombinePipelineLayouts({ layouts, 2u }, pl);
            const RenderHandleReference plRhr = CreatePipelineLayout({ fullName, pl });
            reflectionPlIndex = RenderHandleUtil::GetIndexPart(plRhr.GetHandle());
        }
    }

    auto const clientHandle = CreateClientData(fullName, RenderHandleType::SHADER_STATE_OBJECT,
        { createInfo.renderSlotId, createInfo.pipelineLayoutIndex, reflectionPlIndex, createInfo.categoryId });

    if (createInfo.pipelineLayoutIndex != INVALID_SM_INDEX) {
        pl_.shaderToIndex[clientHandle] = createInfo.pipelineLayoutIndex;
    }
    if (createInfo.vertexInputDeclarationIndex != INVALID_SM_INDEX) {
        shaderVid_.shaderToIndex[clientHandle] = createInfo.vertexInputDeclarationIndex;
    }

    {
        const auto lock = std::lock_guard(pendingMutex_);
        pendingAllocations_.shaders.push_back({ clientHandle, createInfo.vertShaderModuleIndex,
            createInfo.fragShaderModuleIndex, createInfo.pipelineLayoutIndex, createInfo.vertexInputDeclarationIndex });
    }

    if ((!createInfo.shaderFileStr.empty()) && RenderHandleUtil::IsValid(clientHandle)) {
        // update shader file always
        handleToShaderDataFile_.insert_or_assign(clientHandle, string(createInfo.shaderFileStr));
    }
    if (!createInfo.materialMetadata.empty()) {
        MaterialMetadata metadata { string(createInfo.materialMetadata), json::value {} };
        if (metadata.json = json::parse(metadata.raw.data()); metadata.json) {
            // update metadata always
            shaderToMetadata_.insert_or_assign(clientHandle, move(metadata));
        } else {
            shaderToMetadata_.erase(clientHandle);
        }
    } else {
        shaderToMetadata_.erase(clientHandle);
    }

    const uint32_t index = RenderHandleUtil::GetIndexPart(clientHandle);
    if (IsShaderFunc(clientHandle) && (index < static_cast<uint32_t>(shaderMappings_.clientData.size()))) {
        auto& nameDataRef = shaderMappings_.nameData[index];
        nameDataRef.path = createInfo.path;
        nameDataRef.variantName = pathCreateInfo.variantName;
        nameDataRef.displayName = pathCreateInfo.displayName;
        auto& clientDataRef = shaderMappings_.clientData[index];
        clientDataRef.graphicsStateIndex = createInfo.graphicsStateIndex;
        clientDataRef.vertexInputDeclarationIndex = createInfo.vertexInputDeclarationIndex;
        // add base shader if given
#if (RENDER_VALIDATION_ENABLED == 1)
        if ((!pathCreateInfo.variantName.empty()) && pathCreateInfo.baseShaderPath.empty()) {
            PLUGIN_LOG_W("RENDER_VALIDATION: base shader path not give to variant (%s %s)", createInfo.path.data(),
                pathCreateInfo.variantName.data());
        }
#endif
        if (!pathCreateInfo.baseShaderPath.empty()) {
            if (const auto baseHandleIter = nameToClientHandle_.find(pathCreateInfo.baseShaderPath);
                baseHandleIter != nameToClientHandle_.cend()) {
                if (RenderHandleUtil::IsValid(baseHandleIter->second)) {
                    clientDataRef.baseShaderHandle = baseHandleIter->second;
                    const uint64_t hash = HashHandleAndSlot(clientDataRef.baseShaderHandle, createInfo.renderSlotId);
                    hashToShaderVariant_[hash] = clientHandle;
                }
            } else {
                PLUGIN_LOG_W("base shader (%s) not found for (%s)", pathCreateInfo.baseShaderPath.data(),
                    createInfo.path.data());
            }
        }
        return clientDataRef.rhr;
    } else {
        return {};
    }
}

void ShaderManager::AddAdditionalNameForHandle(const RenderHandleReference& handle, const string_view name)
{
    if (handle) {
        const RenderHandle rawHandle = handle.GetHandle();
        const RenderHandleType handleType = RenderHandleUtil::GetHandleType(rawHandle);
        // add name only if name not used yet
        if ((handleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) ||
            (handleType == RenderHandleType::SHADER_STATE_OBJECT)) {
            if (!nameToClientHandle_.contains(name)) {
                nameToClientHandle_[name] = rawHandle;
            } else {
                PLUGIN_LOG_W("trying to add additional name (%s) for shader handle, but the name is already in use",
                    name.data());
            }
        }
    }
}

RenderHandleReference ShaderManager::CreateComputeShader(
    const ComputeShaderCreateInfo& createInfo, const string_view baseShaderPath, const string_view variantName)
{
    if (createInfo.shaderPaths.size() >= 1u) {
        if (const uint32_t moduleIdx = GetShaderModuleIndex(createInfo.shaderPaths[0].path);
            moduleIdx != INVALID_SM_INDEX) {
            return Create(ComputeShaderCreateData { createInfo.path, createInfo.renderSlotId, createInfo.categoryId,
                              RenderHandleUtil::GetIndexPart(createInfo.pipelineLayout), moduleIdx, {}, {} },
                { baseShaderPath, variantName, {} });
        } else {
            PLUGIN_LOG_E("ShaderManager: compute shader (%s) creation failed, compute shader path (%s) not found",
                string(createInfo.path).c_str(), string(createInfo.shaderPaths[0].path).c_str());
        }
    } else {
        PLUGIN_LOG_E("ShaderManager: compute shader (%s) creation failed, no shader module paths given",
            string(createInfo.path).c_str());
    }
    return {};
}

RenderHandleReference ShaderManager::CreateComputeShader(const ComputeShaderCreateInfo& createInfo)
{
    return CreateComputeShader(createInfo, "", "");
}

RenderHandleReference ShaderManager::CreateShader(
    const ShaderCreateInfo& createInfo, const string_view baseShaderPath, const string_view variantName)
{
    if (createInfo.shaderPaths.size() >= 2u) {
        const uint32_t vertShaderModule = GetShaderModuleIndex(createInfo.shaderPaths[0u].path);
        const uint32_t fragShaderModule = GetShaderModuleIndex(createInfo.shaderPaths[1u].path);
        if ((vertShaderModule != INVALID_SM_INDEX) && (fragShaderModule != INVALID_SM_INDEX)) {
            return Create(ShaderCreateData { createInfo.path, createInfo.renderSlotId, createInfo.categoryId,
                              RenderHandleUtil::GetIndexPart(createInfo.vertexInputDeclaration),
                              RenderHandleUtil::GetIndexPart(createInfo.pipelineLayout),
                              RenderHandleUtil::GetIndexPart(createInfo.graphicsState), vertShaderModule,
                              fragShaderModule, {}, {} },
                { baseShaderPath, variantName, {} });
        } else {
            PLUGIN_LOG_E("ShaderManager: shader (%s) creation failed, shader path (vert:%s) (frag:%s) not found",
                string(createInfo.path).c_str(), string(createInfo.shaderPaths[0u].path).c_str(),
                string(createInfo.shaderPaths[1u].path).c_str());
        }
    } else {
        PLUGIN_LOG_E("ShaderManager: shader (%s) creation failed, no shader module paths given",
            string(createInfo.path).c_str());
    }
    return {};
}

RenderHandleReference ShaderManager::CreateShader(const ShaderCreateInfo& createInfo)
{
    return CreateShader(createInfo, "", "");
}

void ShaderManager::HandlePendingAllocations()
{
    pendingMutex_.lock();
    decltype(pendingAllocations_) pendingAllocations = move(pendingAllocations_);
    pendingMutex_.unlock();

    for (const auto& handleRef : pendingAllocations.destroyHandles) {
        const RenderHandleType handleType = RenderHandleUtil::GetHandleType(handleRef);
        const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handleRef);
        if (handleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) {
            if (arrayIndex < static_cast<uint32_t>(computeShaders_.size())) {
                computeShaders_[arrayIndex] = {};
            }
        } else if (handleType == RenderHandleType::SHADER_STATE_OBJECT) {
            if (arrayIndex < static_cast<uint32_t>(shaders_.size())) {
                shaders_[arrayIndex] = {};
            }
        }
    }
    HandlePendingShaders(pendingAllocations);
    HandlePendingModules(pendingAllocations);

    const uint64_t frameCount = device_.GetFrameCount();
    constexpr uint64_t additionalFrameCount { 2u };
    const auto minAge = device_.GetCommandBufferingCount() + additionalFrameCount;
    const auto ageLimit = (frameCount < minAge) ? 0 : (frameCount - minAge);
    auto CompareForErase = [](const auto ageLimit, auto& vec) {
        for (auto iter = vec.begin(); iter != vec.end();) {
            if (iter->frameIndex < ageLimit) {
                iter = vec.erase(iter);
            } else {
                ++iter;
            }
        }
    };
    CompareForErase(ageLimit, deferredDestructions_.shaderModules);
    CompareForErase(ageLimit, deferredDestructions_.computePrograms);
    CompareForErase(ageLimit, deferredDestructions_.shaderPrograms);

    std::swap(reloadedShadersForBackend_, reloadedShaders_);
    reloadedShaders_.clear();
}

void ShaderManager::HandlePendingShaders(Allocs& allocs)
{
    const uint64_t frameCount = device_.GetFrameCount();
    for (const auto& ref : allocs.computeShaders) {
        const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(ref.handle);
        ShaderModule* shaderModule = GetShaderModule(ref.computeModuleIndex);
        if (shaderModule) {
            if (arrayIndex < static_cast<uint32_t>(computeShaders_.size())) {
                // replace with new (push old for deferred destruction)
                deferredDestructions_.computePrograms.push_back({ frameCount, move(computeShaders_[arrayIndex].gsp) });
                computeShaders_[arrayIndex] = { device_.CreateGpuComputeProgram({ shaderModule }),
                    ref.pipelineLayoutIndex, ref.computeModuleIndex };
            } else {
                // new gpu resource
                computeShaders_.push_back({ device_.CreateGpuComputeProgram({ shaderModule }), ref.pipelineLayoutIndex,
                    ref.computeModuleIndex });
            }
        }
#if (RENDER_VALIDATION_ENABLED == 1)
        if (!shaderModule) {
            PLUGIN_LOG_E("RENDER_VALIDATION: Compute shader module with index:%u, not found", ref.computeModuleIndex);
        }
#endif
    }
    for (const auto& ref : allocs.shaders) {
        uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(ref.handle);
        ShaderModule* vertShaderModule = GetShaderModule(ref.vertModuleIndex);
        ShaderModule* fragShaderModule = GetShaderModule(ref.fragModuleIndex);
        if (vertShaderModule && fragShaderModule) {
            if ((arrayIndex < static_cast<uint32_t>(shaders_.size()))) {
                // replace with new (push old for deferred destruction)
                deferredDestructions_.shaderPrograms.push_back({ frameCount, move(shaders_[arrayIndex].gsp) });
                shaders_[arrayIndex] = { device_.CreateGpuShaderProgram({ vertShaderModule, fragShaderModule }),
                    ref.pipelineLayoutIndex, ref.vertexInputDeclIndex, ref.vertModuleIndex, ref.fragModuleIndex };
            } else { // new gpu resource
                shaders_.push_back({ device_.CreateGpuShaderProgram({ vertShaderModule, fragShaderModule }),
                    ref.pipelineLayoutIndex, ref.vertexInputDeclIndex, ref.vertModuleIndex, ref.fragModuleIndex });
            }
        }
#if (RENDER_VALIDATION_ENABLED == 1)
        if ((!vertShaderModule) || (!fragShaderModule)) {
            PLUGIN_LOG_E("RENDER_VALIDATION: Shader module with index: %u or %u, not found", ref.vertModuleIndex,
                ref.fragModuleIndex);
        }
#endif
    }
}

void ShaderManager::HandlePendingModules(Allocs& allocs)
{
    const uint64_t frameCount = device_.GetFrameCount();
    for (const auto modIdx : allocs.recreatedComputeModuleIndices) {
        for (auto& shaderRef : computeShaders_) {
            if (modIdx == shaderRef.compModuleIndex) {
                if (ShaderModule* compModule = GetShaderModule(shaderRef.compModuleIndex); compModule) {
                    deferredDestructions_.computePrograms.push_back({ frameCount, move(shaderRef.gsp) });
                    shaderRef.gsp = device_.CreateGpuComputeProgram({ compModule });
                }
            }
        }
    }
    for (const auto modIdx : allocs.recreatedShaderModuleIndices) {
        for (auto& shaderRef : shaders_) {
            if ((modIdx == shaderRef.vertModuleIndex) || (modIdx == shaderRef.fragModuleIndex)) {
                ShaderModule* vertModule = GetShaderModule(shaderRef.vertModuleIndex);
                ShaderModule* fragModule = GetShaderModule(shaderRef.fragModuleIndex);
                if (vertModule && fragModule) {
                    deferredDestructions_.shaderPrograms.push_back({ frameCount, move(shaderRef.gsp) });
                    shaderRef.gsp = device_.CreateGpuShaderProgram({ vertModule, fragModule });
                }
            }
        }
    }
}

RenderHandleReference ShaderManager::GetShaderHandle(const string_view path) const
{
    const RenderHandle handle = GetHandle(path, nameToClientHandle_);
    const RenderHandleType handleType = RenderHandleUtil::GetHandleType(handle);
    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    if ((handleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) &&
        (index < static_cast<uint32_t>(computeShaderMappings_.clientData.size()))) {
        return computeShaderMappings_.clientData[index].rhr;
    } else if ((handleType == RenderHandleType::SHADER_STATE_OBJECT) &&
               (index < static_cast<uint32_t>(shaderMappings_.clientData.size()))) {
        return shaderMappings_.clientData[index].rhr;
    } else {
        PLUGIN_LOG_W("ShaderManager: invalid shader %s", path.data());
        return {};
    }
}

RenderHandleReference ShaderManager::GetShaderHandle(const string_view path, const string_view variantName) const
{
    const string fullName = path + variantName;
    const RenderHandle handle = GetHandle(fullName, nameToClientHandle_);
    const RenderHandleType handleType = RenderHandleUtil::GetHandleType(handle);
    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    if ((handleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) &&
        (index < static_cast<uint32_t>(computeShaderMappings_.clientData.size()))) {
        return computeShaderMappings_.clientData[index].rhr;
    } else if ((handleType == RenderHandleType::SHADER_STATE_OBJECT) &&
               (index < static_cast<uint32_t>(shaderMappings_.clientData.size()))) {
        return shaderMappings_.clientData[index].rhr;
    } else {
        PLUGIN_LOG_W("ShaderManager: invalid shader (%s) variant (%s)", path.data(), variantName.data());
        return {};
    }
}

RenderHandleReference ShaderManager::GetShaderHandle(const RenderHandle& handle, const uint32_t renderSlotId) const
{
    const RenderHandleType handleType = RenderHandleUtil::GetHandleType(handle);
    if ((handleType != RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) &&
        (handleType != RenderHandleType::SHADER_STATE_OBJECT)) {
        return {}; // early out
    }

    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    RenderHandle baseShaderHandle;
    // check first for own validity and possible base shader handle
    if ((handleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) &&
        (index < static_cast<uint32_t>(computeShaderMappings_.clientData.size()))) {
        const auto& ref = computeShaderMappings_.clientData[index];
        if (ref.renderSlotId == renderSlotId) {
            return ref.rhr;
        }
        baseShaderHandle = ref.baseShaderHandle;
    } else if ((handleType == RenderHandleType::SHADER_STATE_OBJECT) &&
               (index < static_cast<uint32_t>(shaderMappings_.clientData.size()))) {
        const auto& ref = shaderMappings_.clientData[index];
        if (ref.renderSlotId == renderSlotId) {
            return ref.rhr;
        }
        baseShaderHandle = ref.baseShaderHandle;
    }
    // try to find a match through base shader variant
    if (RenderHandleUtil::IsValid(baseShaderHandle)) {
        const uint64_t hash = HashHandleAndSlot(baseShaderHandle, renderSlotId);
        if (const auto iter = hashToShaderVariant_.find(hash); iter != hashToShaderVariant_.cend()) {
            const RenderHandleType baseHandleType = RenderHandleUtil::GetHandleType(iter->second);
            const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(iter->second);
            if ((baseHandleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) &&
                (arrayIndex < computeShaderMappings_.clientData.size())) {
                PLUGIN_ASSERT(computeShaderMappings_.clientData[arrayIndex].renderSlotId == renderSlotId);
                return computeShaderMappings_.clientData[arrayIndex].rhr;
            } else if ((baseHandleType == RenderHandleType::SHADER_STATE_OBJECT) &&
                       (arrayIndex < shaderMappings_.clientData.size())) {
                PLUGIN_ASSERT(shaderMappings_.clientData[arrayIndex].renderSlotId == renderSlotId);
                return shaderMappings_.clientData[arrayIndex].rhr;
            }
        }
    }
    return {};
}

RenderHandleReference ShaderManager::GetShaderHandle(
    const RenderHandleReference& handle, const uint32_t renderSlotId) const
{
    return GetShaderHandle(handle.GetHandle(), renderSlotId);
}

vector<RenderHandleReference> ShaderManager::GetShaders(const uint32_t renderSlotId) const
{
    vector<RenderHandleReference> shaders;
    GetShadersBySlot(renderSlotId, shaderMappings_, shaders);
    GetShadersBySlot(renderSlotId, computeShaderMappings_, shaders);
    return shaders;
}

vector<RenderHandle> ShaderManager::GetShaderRawHandles(const uint32_t renderSlotId) const
{
    vector<RenderHandle> shaders;
    GetShadersBySlot(renderSlotId, shaderMappings_, shaders);
    GetShadersBySlot(renderSlotId, computeShaderMappings_, shaders);
    return shaders;
}

RenderHandleReference ShaderManager::CreateGraphicsState(
    const GraphicsStateCreateInfo& createInfo, const GraphicsStateVariantCreateInfo& variantCreateInfo)
{
    PLUGIN_ASSERT(graphicsStates_.rhr.size() == graphicsStates_.graphicsStates.size());
    const uint32_t renderSlotId = CreateRenderSlotId(variantCreateInfo.renderSlot);
    // NOTE: No collisions expected if path is used
    const string fullName = createInfo.path + variantCreateInfo.variant;
    uint32_t arrayIndex = INVALID_SM_INDEX;
    if (auto nameIter = graphicsStates_.nameToIndex.find(fullName); nameIter != graphicsStates_.nameToIndex.end()) {
        arrayIndex = static_cast<uint32_t>(nameIter->second);
    }

    uint32_t baseVariantIndex = INVALID_SM_INDEX;
    RenderHandleReference rhr;
    if (arrayIndex < graphicsStates_.rhr.size()) {
        rhr = graphicsStates_.rhr[arrayIndex];
        graphicsStates_.graphicsStates[arrayIndex] = createInfo.graphicsState;
        const uint64_t hash = HashGraphicsState(createInfo.graphicsState);
        baseVariantIndex = GetBaseGraphicsStateVariantIndex(graphicsStates_, variantCreateInfo);
        graphicsStates_.data[arrayIndex] = { hash, renderSlotId, baseVariantIndex, variantCreateInfo.stateFlags };
        graphicsStates_.hashToIndex[hash] = arrayIndex;
    } else { // new
        arrayIndex = static_cast<uint32_t>(graphicsStates_.rhr.size());
        // NOTE: these are only updated for new states
        if (!fullName.empty()) {
            graphicsStates_.nameToIndex[fullName] = arrayIndex;
        }
        const RenderHandle handle = RenderHandleUtil::CreateHandle(RenderHandleType::GRAPHICS_STATE, arrayIndex);
        graphicsStates_.rhr.push_back(
            RenderHandleReference(handle, IRenderReferenceCounter::Ptr(new ShaderReferenceCounter())));
        rhr = graphicsStates_.rhr[arrayIndex];
        graphicsStates_.graphicsStates.push_back(createInfo.graphicsState);
        const uint64_t hash = HashGraphicsState(createInfo.graphicsState);
        // ordering matters, this fetches from nameToIndex
        baseVariantIndex = GetBaseGraphicsStateVariantIndex(graphicsStates_, variantCreateInfo);
        graphicsStates_.data.push_back({ hash, renderSlotId, baseVariantIndex, variantCreateInfo.stateFlags });
        graphicsStates_.hashToIndex[hash] = arrayIndex;
    }
    if (baseVariantIndex < graphicsStates_.rhr.size()) {
        const uint64_t variantHash = HashHandleAndSlot(graphicsStates_.rhr[baseVariantIndex].GetHandle(), renderSlotId);
        if (variantHash != INVALID_SM_INDEX) {
#if (RENDER_VALIDATION_ENABLED == 1)
            if (graphicsStates_.variantHashToIndex.contains(variantHash)) {
                PLUGIN_LOG_W("RENDER_VALIDATION: overwriting variant hash with %s %s", createInfo.path.data(),
                    variantCreateInfo.variant.data());
            }
#endif
            graphicsStates_.variantHashToIndex[variantHash] = RenderHandleUtil::GetIndexPart(rhr.GetHandle());
        }
    }

    return rhr;
}

RenderHandleReference ShaderManager::CreateGraphicsState(const GraphicsStateCreateInfo& createInfo)
{
    return CreateGraphicsState(createInfo, {});
}

RenderHandleReference ShaderManager::GetGraphicsStateHandle(const string_view path) const
{
    if (const auto iter = graphicsStates_.nameToIndex.find(path); iter != graphicsStates_.nameToIndex.cend()) {
        PLUGIN_ASSERT(iter->second < graphicsStates_.rhr.size());
        return graphicsStates_.rhr[iter->second];
    } else {
        PLUGIN_LOG_W("ShaderManager: named graphics state not found: %s", string(path).c_str());
        return {};
    }
}

RenderHandleReference ShaderManager::GetGraphicsStateHandle(const string_view path, const string_view variantName) const
{
    // NOTE: does not call the base GetGraphicsStateHandle due to better error logging
    const string fullName = string(path + variantName);
    if (const auto iter = graphicsStates_.nameToIndex.find(fullName); iter != graphicsStates_.nameToIndex.cend()) {
        PLUGIN_ASSERT(iter->second < graphicsStates_.rhr.size());
        return graphicsStates_.rhr[iter->second];
    } else {
        PLUGIN_LOG_W(
            "ShaderManager: named graphics state not found (name: %s variant: %s)", path.data(), variantName.data());
        return {};
    }
}

RenderHandleReference ShaderManager::GetGraphicsStateHandle(
    const RenderHandle& handle, const uint32_t renderSlotId) const
{
    if (RenderHandleUtil::GetHandleType(handle) == RenderHandleType::GRAPHICS_STATE) {
        const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
        if (arrayIndex < static_cast<uint32_t>(graphicsStates_.data.size())) {
            // check for own validity
            const auto& data = graphicsStates_.data[arrayIndex];
            if (renderSlotId == data.renderSlotId) {
                return graphicsStates_.rhr[arrayIndex];
            }
            // check for base variant for hashing
            if (data.baseVariantIndex < static_cast<uint32_t>(graphicsStates_.data.size())) {
                const RenderHandle baseHandle = graphicsStates_.rhr[data.baseVariantIndex].GetHandle();
                const uint64_t hash = HashHandleAndSlot(baseHandle, renderSlotId);
                if (const auto iter = graphicsStates_.variantHashToIndex.find(hash);
                    iter != graphicsStates_.variantHashToIndex.cend()) {
                    PLUGIN_ASSERT(iter->second < static_cast<uint32_t>(graphicsStates_.rhr.size()));
                    return graphicsStates_.rhr[iter->second];
                }
            }
        }
    }
    return {};
}

RenderHandleReference ShaderManager::GetGraphicsStateHandle(
    const RenderHandleReference& handle, const uint32_t renderSlotId) const
{
    return GetGraphicsStateHandle(handle.GetHandle(), renderSlotId);
}

RenderHandleReference ShaderManager::GetGraphicsStateHandleByHash(const uint64_t hash) const
{
    if (const auto iter = graphicsStates_.hashToIndex.find(hash); iter != graphicsStates_.hashToIndex.cend()) {
        PLUGIN_ASSERT(iter->second < graphicsStates_.rhr.size());
        return graphicsStates_.rhr[iter->second];
    } else {
        return {};
    }
}

RenderHandleReference ShaderManager::GetGraphicsStateHandleByShaderHandle(const RenderHandle& handle) const
{
    if (RenderHandleUtil::GetHandleType(handle) == RenderHandleType::SHADER_STATE_OBJECT) {
        const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
        if (arrayIndex < static_cast<uint32_t>(shaderMappings_.clientData.size())) {
            const uint32_t gsIndex = shaderMappings_.clientData[arrayIndex].graphicsStateIndex;
            if (gsIndex < static_cast<uint32_t>(graphicsStates_.graphicsStates.size())) {
                return graphicsStates_.rhr[gsIndex];
            }
#if (RENDER_VALIDATION_ENABLED == 1)
            PLUGIN_ASSERT(gsIndex != INVALID_SM_INDEX); // not and optional index ATM
            PLUGIN_ASSERT(gsIndex < graphicsStates_.rhr.size());
#endif
        }
    }
    return {};
}

RenderHandleReference ShaderManager::GetGraphicsStateHandleByShaderHandle(const RenderHandleReference& handle) const
{
    return GetGraphicsStateHandleByShaderHandle(handle.GetHandle());
}

GraphicsState ShaderManager::GetGraphicsState(const RenderHandleReference& handle) const
{
    return GetGraphicsStateRef(handle);
}

vector<RenderHandleReference> ShaderManager::GetGraphicsStates(const uint32_t renderSlotId) const
{
    vector<RenderHandleReference> gfxStates;
    GetGraphicsStatesBySlot(renderSlotId, graphicsStates_, gfxStates);
    return gfxStates;
}

const GraphicsState& ShaderManager::GetGraphicsStateRef(const RenderHandle& handle) const
{
    const RenderHandleType type = RenderHandleUtil::GetHandleType(handle);
    const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
    if ((type == RenderHandleType::GRAPHICS_STATE) &&
        (arrayIndex < static_cast<uint32_t>(graphicsStates_.graphicsStates.size()))) {
        return graphicsStates_.graphicsStates[arrayIndex];
    } else {
#if (RENDER_VALIDATION_ENABLED == 1)
        if (RenderHandleUtil::IsValid(handle) && (type != RenderHandleType::GRAPHICS_STATE)) {
            PLUGIN_LOG_W("RENDER_VALIDATION: invalid handle type given to GetGraphicsState()");
        }
#endif
        return defaultGraphicsState_;
    }
}

const GraphicsState& ShaderManager::GetGraphicsStateRef(const RenderHandleReference& handle) const
{
    return GetGraphicsStateRef(handle.GetHandle());
}

uint32_t ShaderManager::GetRenderSlotId(const string_view renderSlot) const
{
    if (const auto iter = renderSlotIds_.nameToId.find(renderSlot); iter != renderSlotIds_.nameToId.cend()) {
        return iter->second;
    } else {
        return INVALID_SM_INDEX;
    }
}

uint32_t ShaderManager::GetRenderSlotId(const RenderHandle& handle) const
{
    uint32_t id = ~0u;
    const RenderHandleType handleType = RenderHandleUtil::GetHandleType(handle);
    const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
    if (handleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) {
        if (arrayIndex < computeShaderMappings_.clientData.size()) {
            id = computeShaderMappings_.clientData[arrayIndex].renderSlotId;
        }
    } else if (handleType == RenderHandleType::SHADER_STATE_OBJECT) {
        if (arrayIndex < shaderMappings_.clientData.size()) {
            id = shaderMappings_.clientData[arrayIndex].renderSlotId;
        }
    } else if (handleType == RenderHandleType::GRAPHICS_STATE) {
        if (arrayIndex < graphicsStates_.data.size()) {
            id = graphicsStates_.data[arrayIndex].renderSlotId;
        }
    }
    return id;
}

uint32_t ShaderManager::GetRenderSlotId(const RenderHandleReference& handle) const
{
    return GetRenderSlotId(handle.GetHandle());
}

IShaderManager::RenderSlotData ShaderManager::GetRenderSlotData(const uint32_t renderSlotId) const
{
    if (renderSlotId < static_cast<uint32_t>(renderSlotIds_.data.size())) {
        return renderSlotIds_.data[renderSlotId];
    } else {
        return {};
    }
}

RenderHandleReference ShaderManager::GetVertexInputDeclarationHandleByShaderHandle(const RenderHandle& handle) const
{
    if (RenderHandleUtil::GetHandleType(handle) == RenderHandleType::SHADER_STATE_OBJECT) {
        const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
        auto& mappings = shaderMappings_;
        if (arrayIndex < mappings.clientData.size()) {
            const uint32_t vidIndex = mappings.clientData[arrayIndex].vertexInputDeclarationIndex;
            if (vidIndex < shaderVid_.rhr.size()) {
                PLUGIN_ASSERT(vidIndex < shaderVid_.rhr.size());
                return shaderVid_.rhr[vidIndex];
            }
        }
    }
    return {};
}

RenderHandleReference ShaderManager::GetVertexInputDeclarationHandleByShaderHandle(
    const RenderHandleReference& handle) const
{
    return GetVertexInputDeclarationHandleByShaderHandle(handle.GetHandle());
}

RenderHandleReference ShaderManager::GetVertexInputDeclarationHandle(const string_view path) const
{
    if (const auto iter = shaderVid_.nameToIndex.find(path); iter != shaderVid_.nameToIndex.cend()) {
        PLUGIN_ASSERT(iter->second < shaderVid_.rhr.size());
        return shaderVid_.rhr[iter->second];
    } else {
        PLUGIN_LOG_W("ShaderManager: vertex input declaration not found: %s", path.data());
        return {};
    }
}

VertexInputDeclarationView ShaderManager::GetVertexInputDeclarationView(const RenderHandle& handle) const
{
    const RenderHandleType type = RenderHandleUtil::GetHandleType(handle);
    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    if ((type == RenderHandleType::VERTEX_INPUT_DECLARATION) &&
        (index < static_cast<uint32_t>(shaderVid_.data.size()))) {
        const auto& ref = shaderVid_.data[index];
        return {
            array_view<const VertexInputDeclaration::VertexInputBindingDescription>(
                ref.bindingDescriptions, ref.bindingDescriptionCount),
            array_view<const VertexInputDeclaration::VertexInputAttributeDescription>(
                ref.attributeDescriptions, ref.attributeDescriptionCount),
        };
    } else {
#if (RENDER_VALIDATION_ENABLED == 1)
        if (RenderHandleUtil::IsValid(handle) && (type != RenderHandleType::VERTEX_INPUT_DECLARATION)) {
            PLUGIN_LOG_W("RENDER_VALIDATION: invalid handle type given to GetVertexInputDeclarationView()");
        }
#endif
        return {};
    }
}

VertexInputDeclarationView ShaderManager::GetVertexInputDeclarationView(const RenderHandleReference& handle) const
{
    return GetVertexInputDeclarationView(handle.GetHandle());
}

RenderHandleReference ShaderManager::CreateVertexInputDeclaration(const VertexInputDeclarationCreateInfo& createInfo)
{
    uint32_t arrayIndex = INVALID_SM_INDEX;
    if (auto nameIter = shaderVid_.nameToIndex.find(createInfo.path); nameIter != shaderVid_.nameToIndex.end()) {
        PLUGIN_ASSERT(nameIter->second < shaderVid_.rhr.size());
        arrayIndex = static_cast<uint32_t>(nameIter->second);
    }
    if (arrayIndex < static_cast<uint32_t>(shaderVid_.data.size())) {
        // inside core validation due to being very low info for common users
#if (RENDER_VALIDATION_ENABLED == 1)
        PLUGIN_LOG_I("ShaderManager: re-creating vertex input declaration (name %s)", createInfo.path.data());
#endif
    } else { // new
        arrayIndex = static_cast<uint32_t>(shaderVid_.data.size());
        const RenderHandle handle =
            RenderHandleUtil::CreateHandle(RenderHandleType::VERTEX_INPUT_DECLARATION, arrayIndex);
        shaderVid_.rhr.push_back(
            RenderHandleReference(handle, IRenderReferenceCounter::Ptr(new ShaderReferenceCounter())));
        shaderVid_.data.push_back(VertexInputDeclarationData {});
        // NOTE: only updated for new
        if (!createInfo.path.empty()) {
            shaderVid_.nameToIndex[createInfo.path] = arrayIndex;
        }
    }

    if (arrayIndex < static_cast<uint32_t>(shaderVid_.data.size())) {
        const VertexInputDeclarationView& vertexInputDeclarationView = createInfo.vertexInputDeclarationView;
        VertexInputDeclarationData& ref = shaderVid_.data[arrayIndex];
        ref.bindingDescriptionCount = static_cast<uint32_t>(vertexInputDeclarationView.bindingDescriptions.size());
        ref.attributeDescriptionCount =
            static_cast<uint32_t>(vertexInputDeclarationView.attributeDescriptions.size());

        PLUGIN_ASSERT(ref.bindingDescriptionCount <= PipelineStateConstants::MAX_VERTEX_BUFFER_COUNT);
        PLUGIN_ASSERT(ref.attributeDescriptionCount <= PipelineStateConstants::MAX_VERTEX_BUFFER_COUNT);

        for (uint32_t idx = 0; idx < ref.bindingDescriptionCount; ++idx) {
            ref.bindingDescriptions[idx] = vertexInputDeclarationView.bindingDescriptions[idx];
        }
        for (uint32_t idx = 0; idx < ref.attributeDescriptionCount; ++idx) {
            ref.attributeDescriptions[idx] = vertexInputDeclarationView.attributeDescriptions[idx];
        }
        return shaderVid_.rhr[arrayIndex];
    } else {
        return {};
    }
}

RenderHandleReference ShaderManager::GetPipelineLayoutHandleByShaderHandle(const RenderHandle& handle) const
{
    const RenderHandleType type = RenderHandleUtil::GetHandleType(handle);
    const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
    if (type == RenderHandleType::SHADER_STATE_OBJECT) {
        auto& mappings = shaderMappings_;
        if (arrayIndex < mappings.clientData.size()) {
            const uint32_t plIndex = mappings.clientData[arrayIndex].pipelineLayoutIndex;
            if (plIndex < static_cast<uint32_t>(pl_.rhr.size())) {
                PLUGIN_ASSERT(plIndex < static_cast<uint32_t>(pl_.rhr.size()));
                return pl_.rhr[plIndex];
            }
        }
    } else if (type == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) {
        auto& mappings = computeShaderMappings_;
        if (arrayIndex < mappings.clientData.size()) {
            const uint32_t plIndex = mappings.clientData[arrayIndex].pipelineLayoutIndex;
            if (plIndex < static_cast<uint32_t>(pl_.rhr.size())) {
                PLUGIN_ASSERT(plIndex < static_cast<uint32_t>(pl_.rhr.size()));
                return pl_.rhr[plIndex];
            }
        }
    }
    return {};
}

RenderHandleReference ShaderManager::GetPipelineLayoutHandleByShaderHandle(const RenderHandleReference& handle) const
{
    return GetPipelineLayoutHandleByShaderHandle(handle.GetHandle());
}

RenderHandleReference ShaderManager::GetPipelineLayoutHandle(const string_view path) const
{
    if (const auto iter = pl_.nameToIndex.find(path); iter != pl_.nameToIndex.cend()) {
        const uint32_t index = iter->second;
        PLUGIN_ASSERT(index < static_cast<uint32_t>(pl_.rhr.size()));
        return pl_.rhr[index];
    } else {
        PLUGIN_LOG_W("ShaderManager: pipeline layout not found: %s", path.data());
        return {};
    }
}

PipelineLayout ShaderManager::GetPipelineLayout(const RenderHandle& handle) const
{
    return GetPipelineLayoutRef(handle);
}

PipelineLayout ShaderManager::GetPipelineLayout(const RenderHandleReference& handle) const
{
    return GetPipelineLayoutRef(handle.GetHandle());
}

const PipelineLayout& ShaderManager::GetPipelineLayoutRef(const RenderHandle& handle) const
{
    const RenderHandleType type = RenderHandleUtil::GetHandleType(handle);
    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    if ((type == RenderHandleType::PIPELINE_LAYOUT) && (index < static_cast<uint32_t>(pl_.data.size()))) {
        return pl_.data[index];
    } else {
#if (RENDER_VALIDATION_ENABLED == 1)
        if (RenderHandleUtil::IsValid(handle) && (type != RenderHandleType::PIPELINE_LAYOUT)) {
            PLUGIN_LOG_W("RENDER_VALIDATION: invalid handle type given to GetPipelineLayout()");
        }
#endif
        return defaultPipelineLayout_;
    }
}

RenderHandleReference ShaderManager::GetReflectionPipelineLayoutHandle(const RenderHandle& handle) const
{
    const RenderHandleType type = RenderHandleUtil::GetHandleType(handle);
    const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
    uint32_t plIndex = INVALID_SM_INDEX;
    if (type == RenderHandleType::SHADER_STATE_OBJECT) {
        if (arrayIndex < shaderMappings_.clientData.size()) {
            plIndex = shaderMappings_.clientData[arrayIndex].reflectionPipelineLayoutIndex;
        }
    } else if (type == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) {
        if (arrayIndex < computeShaderMappings_.clientData.size()) {
            plIndex = computeShaderMappings_.clientData[arrayIndex].reflectionPipelineLayoutIndex;
        }
    }

    if (plIndex < pl_.data.size()) {
        return pl_.rhr[plIndex];
    } else {
#if (RENDER_VALIDATION_ENABLED == 1)
        PLUGIN_LOG_W("RENDER_VALIDATION: ShaderManager, invalid shader handle for GetReflectionPipelineLayoutHandle");
#endif
        return {};
    }
}

RenderHandleReference ShaderManager::GetReflectionPipelineLayoutHandle(const RenderHandleReference& handle) const
{
    return GetReflectionPipelineLayoutHandle(handle.GetHandle());
}

PipelineLayout ShaderManager::GetReflectionPipelineLayout(const RenderHandleReference& handle) const
{
    return GetReflectionPipelineLayoutRef(handle.GetHandle());
}

const PipelineLayout& ShaderManager::GetReflectionPipelineLayoutRef(const RenderHandle& handle) const
{
    const RenderHandleType type = RenderHandleUtil::GetHandleType(handle);
    const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
    uint32_t plIndex = INVALID_SM_INDEX;
    if (type == RenderHandleType::SHADER_STATE_OBJECT) {
        if (arrayIndex < shaderMappings_.clientData.size()) {
            plIndex = shaderMappings_.clientData[arrayIndex].reflectionPipelineLayoutIndex;
        }
    } else if (type == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) {
        if (arrayIndex < computeShaderMappings_.clientData.size()) {
            plIndex = computeShaderMappings_.clientData[arrayIndex].reflectionPipelineLayoutIndex;
        }
    }

    if (plIndex < pl_.data.size()) {
        return pl_.data[plIndex];
    } else {
#if (RENDER_VALIDATION_ENABLED == 1)
        PLUGIN_LOG_W("RENDER_VALIDATION: ShaderManager, invalid shader handle for GetReflectionPipelineLayout");
#endif
        return defaultPipelineLayout_;
    }
}

ShaderSpecializationConstantView ShaderManager::GetReflectionSpecialization(const RenderHandle& handle) const
{
    const RenderHandleType type = RenderHandleUtil::GetHandleType(handle);
    const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
    if (type == RenderHandleType::SHADER_STATE_OBJECT) {
        // NOTE: at the moment there might not be availability yet, will be FIXED
        if (arrayIndex < shaders_.size()) {
            if (shaders_[arrayIndex].gsp) {
                return shaders_[arrayIndex].gsp->GetReflection().shaderSpecializationConstantView;
            }
        }
    } else if (type == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) {
        // NOTE: at the moment there might not be availability yet, will be FIXED
        if (arrayIndex < computeShaders_.size()) {
            if (computeShaders_[arrayIndex].gsp) {
                return computeShaders_[arrayIndex].gsp->GetReflection().shaderSpecializationConstantView;
            }
        }
    }
#if (RENDER_VALIDATION_ENABLED == 1)
    PLUGIN_LOG_W("RENDER_VALIDATION: ShaderManager, invalid shader handle for GetReflectionSpecialization");
#endif
    return defaultSSCV_;
}

ShaderSpecializationConstantView ShaderManager::GetReflectionSpecialization(const RenderHandleReference& handle) const
{
    return GetReflectionSpecialization(handle.GetHandle());
}

VertexInputDeclarationView ShaderManager::GetReflectionVertexInputDeclaration(const RenderHandle& handle) const
{
    const RenderHandleType type = RenderHandleUtil::GetHandleType(handle);
    const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
    if (type == RenderHandleType::SHADER_STATE_OBJECT) {
        // NOTE: at the moment there might not be availability yet, will be FIXED
        if (arrayIndex < shaders_.size()) {
            if (shaders_[arrayIndex].gsp) {
                return shaders_[arrayIndex].gsp->GetReflection().vertexInputDeclarationView;
            }
        }
    }
#if (RENDER_VALIDATION_ENABLED == 1)
    PLUGIN_LOG_W("RENDER_VALIDATION: ShaderManager, invalid shader handle for GetReflectionVertexInputDeclaration");
#endif
    return defaultVIDV_;
}

VertexInputDeclarationView ShaderManager::GetReflectionVertexInputDeclaration(const RenderHandleReference& handle) const
{
    return GetReflectionVertexInputDeclaration(handle.GetHandle());
}

ShaderThreadGroup ShaderManager::GetReflectionThreadGroupSize(const RenderHandle& handle) const
{
    const RenderHandleType type = RenderHandleUtil::GetHandleType(handle);
    const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
    if (type == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) {
        // NOTE: at the moment there might not be availability yet, will be FIXED
        if (arrayIndex < computeShaders_.size()) {
            if (computeShaders_[arrayIndex].gsp) {
                const auto& refl = computeShaders_[arrayIndex].gsp->GetReflection();
                return { refl.threadGroupSizeX, refl.threadGroupSizeY, refl.threadGroupSizeZ };
            }
        }
    }
#if (RENDER_VALIDATION_ENABLED == 1)
    PLUGIN_LOG_W("RENDER_VALIDATION: ShaderManager, invalid shader handle for GetReflectionThreadGroupSize");
#endif
    return defaultSTG_;
}

ShaderThreadGroup ShaderManager::GetReflectionThreadGroupSize(const RenderHandleReference& handle) const
{
    return GetReflectionThreadGroupSize(handle.GetHandle());
}

RenderHandleReference ShaderManager::CreatePipelineLayout(const PipelineLayoutCreateInfo& createInfo)
{
    uint32_t arrayIndex = INVALID_SM_INDEX;
    if (auto nameIter = pl_.nameToIndex.find(createInfo.path); nameIter != pl_.nameToIndex.end()) {
        PLUGIN_ASSERT(nameIter->second < pl_.rhr.size());
        arrayIndex = static_cast<uint32_t>(nameIter->second);
    }

    if (arrayIndex < static_cast<uint32_t>(pl_.data.size())) { // replace
        // inside core validation due to being very low info for common users
#if (RENDER_VALIDATION_ENABLED == 1)
        PLUGIN_LOG_I("ShaderManager: re-creating pipeline layout (name %s)", createInfo.path.data());
#endif
    } else { // new
        arrayIndex = static_cast<uint32_t>(pl_.data.size());
        pl_.data.push_back(PipelineLayout {});
        // NOTE: only updated for new (should check with re-creation)
        if (!createInfo.path.empty()) {
            pl_.nameToIndex[createInfo.path] = arrayIndex;
        }
        pl_.rhr.push_back(RenderHandleReference {});
    }

    if (arrayIndex < static_cast<uint32_t>(pl_.data.size())) {
        const PipelineLayout& pipelineLayout = createInfo.pipelineLayout;
        PipelineLayout& ref = pl_.data[arrayIndex];
#if (RENDER_VALIDATION_ENABLED == 1)
        if (pipelineLayout.descriptorSetCount > PipelineLayoutConstants::MAX_DESCRIPTOR_SET_COUNT ||
            pipelineLayout.pushConstant.byteSize > PipelineLayoutConstants::MAX_PUSH_CONSTANT_BYTE_SIZE) {
            PLUGIN_LOG_W(
                "Invalid pipeline layout sizes clamped (name:%s). Set count %u <= %u, push constant size %u <= %u",
                createInfo.path.data(), ref.descriptorSetCount, PipelineLayoutConstants::MAX_DESCRIPTOR_SET_COUNT,
                pipelineLayout.pushConstant.byteSize, PipelineLayoutConstants::MAX_PUSH_CONSTANT_BYTE_SIZE);
        }
#endif
        ref.pushConstant = pipelineLayout.pushConstant;
        ref.descriptorSetCount =
            Math::min(PipelineLayoutConstants::MAX_DESCRIPTOR_SET_COUNT, pipelineLayout.descriptorSetCount);
        ref.pushConstant.byteSize =
            Math::min(PipelineLayoutConstants::MAX_PUSH_CONSTANT_BYTE_SIZE, pipelineLayout.pushConstant.byteSize);
        uint32_t descriptorSetBitmask = 0;
        // can be user generated pipeline layout (i.e. set index might be different than index)
        for (uint32_t idx = 0; idx < PipelineLayoutConstants::MAX_DESCRIPTOR_SET_COUNT; ++idx) {
            const uint32_t setIdx = pipelineLayout.descriptorSetLayouts[idx].set;
            if (setIdx < PipelineLayoutConstants::MAX_DESCRIPTOR_SET_COUNT) {
                ref.descriptorSetLayouts[setIdx] = pipelineLayout.descriptorSetLayouts[setIdx];
                descriptorSetBitmask |= (1 << setIdx);
            }
        }

        const RenderHandle handle =
            RenderHandleUtil::CreateHandle(RenderHandleType::PIPELINE_LAYOUT, arrayIndex, 0, descriptorSetBitmask);
        pl_.rhr[arrayIndex] = RenderHandleReference(handle, IRenderReferenceCounter::Ptr(new ShaderReferenceCounter()));
        return pl_.rhr[arrayIndex];
    } else {
        return {};
    }
}

const GpuComputeProgram* ShaderManager::GetGpuComputeProgram(const RenderHandle& handle) const
{
    if (!IsComputeShaderFunc(handle)) {
        PLUGIN_LOG_E("ShaderManager: invalid compute shader handle");
        return nullptr;
    }
    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    if (index < static_cast<uint32_t>(computeShaders_.size())) {
        return computeShaders_[index].gsp.get();
    } else {
        PLUGIN_LOG_E("ShaderManager: invalid compute shader handle");
        return nullptr;
    }
}

const GpuShaderProgram* ShaderManager::GetGpuShaderProgram(const RenderHandle& handle) const
{
    if (!IsShaderFunc(handle)) {
        PLUGIN_LOG_E("ShaderManager: invalid shader handle");
        return nullptr;
    }
    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    if (index < static_cast<uint32_t>(shaders_.size())) {
        return shaders_[index].gsp.get();
    } else {
        PLUGIN_LOG_E("ShaderManager: invalid shader handle");
        return nullptr;
    }
}

uint32_t ShaderManager::CreateShaderModule(const string_view path, const ShaderModuleCreateInfo& createInfo)
{
    auto& nameToIdx = shaderModules_.nameToIndex;
    auto& modules = shaderModules_.shaderModules;
    if (auto iter = nameToIdx.find(path); iter != nameToIdx.end()) {
        PLUGIN_ASSERT(iter->second < modules.size());
        // inside core validation due to being very low info for common users
#if (RENDER_VALIDATION_ENABLED == 1)
        PLUGIN_LOG_I("ShaderManager: re-creating shader module %s", path.data());
#endif
        // check that we don't push the same indices multiple times
        bool found = false;
        for (const auto& ref : pendingAllocations_.recreatedShaderModuleIndices) {
            if (ref == iter->second) {
                found = true;
                break;
            }
        }
        if (!found) {
            pendingAllocations_.recreatedShaderModuleIndices.push_back(iter->second);
        }
        deferredDestructions_.shaderModules.push_back({ device_.GetFrameCount(), move(modules[iter->second]) });
        modules[iter->second] = device_.CreateShaderModule(createInfo);
        return iter->second;
    } else {
        const uint32_t idx = static_cast<uint32_t>(modules.size());
        if (!path.empty()) {
            nameToIdx[path] = idx;
        }
        modules.push_back(device_.CreateShaderModule(createInfo));
        return idx;
    }
}

ShaderModule* ShaderManager::GetShaderModule(const uint32_t index) const
{
    const auto& modules = shaderModules_.shaderModules;
    if (index < modules.size()) {
        return modules[index].get();
    } else {
        return nullptr;
    }
}

uint32_t ShaderManager::GetShaderModuleIndex(const string_view path) const
{
    const auto& nameToIdx = shaderModules_.nameToIndex;
    if (const auto iter = nameToIdx.find(path); iter != nameToIdx.cend()) {
        PLUGIN_ASSERT(iter->second < shaderModules_.shaderModules.size());
        return iter->second;
    } else {
        return INVALID_SM_INDEX;
    }
}

bool ShaderManager::IsComputeShader(const RenderHandleReference& handle) const
{
    return IsComputeShaderFunc(handle.GetHandle());
}

bool ShaderManager::IsShader(const RenderHandleReference& handle) const
{
    return IsShaderFunc(handle.GetHandle());
}

void ShaderManager::LoadShaderFiles(const ShaderFilePathDesc& desc)
{
    if (shaderLoader_) {
        shaderLoader_->Load(desc);
    }
}

void ShaderManager::LoadShaderFile(const string_view uri)
{
    if (shaderLoader_ && (!uri.empty())) {
        shaderLoader_->LoadFile(uri, false);
    }
}

void ShaderManager::UnloadShaderFiles(const ShaderFilePathDesc& desc) {}

void ShaderManager::ReloadShaderFile(const string_view uri)
{
    if (shaderLoader_ && (!uri.empty())) {
        shaderLoader_->LoadFile(uri, true);
        if (const auto iter = nameToClientHandle_.find(uri); iter != nameToClientHandle_.cend()) {
            reloadedShaders_.push_back(iter->second);
        }
    }
}

bool ShaderManager::HasReloadedShaderForBackend() const
{
    return !reloadedShadersForBackend_.empty();
}

BASE_NS::array_view<const RenderHandle> ShaderManager::GetReloadedShadersForBackend() const
{
    return reloadedShadersForBackend_;
}

const BASE_NS::string_view ShaderManager::GetShaderFile(const RenderHandleReference& handle) const
{
    if (const auto iter = handleToShaderDataFile_.find(handle.GetHandle()); iter != handleToShaderDataFile_.cend()) {
        return iter->second;
    }
    return {};
}

const json::value* ShaderManager::GetMaterialMetadata(const RenderHandleReference& handle) const
{
    if (const auto iter = shaderToMetadata_.find(handle.GetHandle()); iter != shaderToMetadata_.end()) {
        return &iter->second.json;
    }
    return nullptr;
}

void ShaderManager::DestroyShader(const RenderHandle handle)
{
    PLUGIN_ASSERT(computeShaderMappings_.clientData.size() == computeShaderMappings_.nameData.size());
    PLUGIN_ASSERT(shaderMappings_.clientData.size() == shaderMappings_.nameData.size());

    auto eraseIndexData = [](auto& mapStore, const RenderHandle handle) {
        if (auto const pos = std::find_if(
            mapStore.begin(), mapStore.end(), [handle](auto const& element) { return element.second == handle; });
            pos != mapStore.end()) {
            mapStore.erase(pos);
        }
    };

    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    if (IsComputeShaderFunc(handle)) {
        auto& mappings = computeShaderMappings_;
        if (index < static_cast<uint32_t>(mappings.clientData.size())) {
            mappings.clientData[index] = {};
            mappings.nameData[index] = {};
            eraseIndexData(nameToClientHandle_, handle);
            {
                const auto lock = std::lock_guard(pendingMutex_);
                pendingAllocations_.destroyHandles.push_back(handle);
            }
        }
    } else if (IsShaderFunc(handle)) {
        auto& mappings = shaderMappings_;
        if (index < static_cast<uint32_t>(mappings.clientData.size())) {
            mappings.clientData[index] = {};
            mappings.nameData[index] = {};
            eraseIndexData(nameToClientHandle_, handle);
            {
                const auto lock = std::lock_guard(pendingMutex_);
                pendingAllocations_.destroyHandles.push_back(handle);
            }
        }
    }
}

void ShaderManager::Destroy(const RenderHandleReference& handle)
{
    const RenderHandle rawHandle = handle.GetHandle();
    const RenderHandleType handleType = RenderHandleUtil::GetHandleType(rawHandle);
    if ((handleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) ||
        (handleType == RenderHandleType::SHADER_STATE_OBJECT)) {
        DestroyShader(rawHandle);
    } else if (handleType == RenderHandleType::GRAPHICS_STATE) {
        DestroyGraphicsState(rawHandle);
    } else if (handleType == RenderHandleType::PIPELINE_LAYOUT) {
        DestroyPipelineLayout(rawHandle);
    } else if (handleType == RenderHandleType::VERTEX_INPUT_DECLARATION) {
        DestroyVertexInputDeclaration(rawHandle);
    }
}

void ShaderManager::DestroyGraphicsState(const RenderHandle handle)
{
    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    if (index < static_cast<uint32_t>(graphicsStates_.rhr.size())) {
        graphicsStates_.rhr[index] = {};
        graphicsStates_.data[index] = {};
        graphicsStates_.graphicsStates[index] = {};

        auto eraseIndexData = [](auto& mapStore, const uint32_t index) {
            if (auto const pos = std::find_if(
                mapStore.begin(), mapStore.end(), [index](auto const& element) { return element.second == index; });
                pos != mapStore.end()) {
                mapStore.erase(pos);
            }
        };
        eraseIndexData(graphicsStates_.nameToIndex, index);
        eraseIndexData(graphicsStates_.hashToIndex, index);
        // NOTE: shaderToStates needs to be added
    }
}

void ShaderManager::DestroyPipelineLayout(const RenderHandle handle)
{
    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    if (index < static_cast<uint32_t>(pl_.rhr.size())) {
        pl_.rhr[index] = {};
        pl_.data[index] = {};

        auto eraseIndexData = [](auto& mapStore, const uint32_t index) {
            if (auto const pos = std::find_if(
                mapStore.begin(), mapStore.end(), [index](auto const& element) { return element.second == index; });
                pos != mapStore.end()) {
                mapStore.erase(pos);
            }
        };
        eraseIndexData(pl_.nameToIndex, index);
        eraseIndexData(pl_.computeShaderToIndex, index);
        eraseIndexData(pl_.shaderToIndex, index);
    }
}

void ShaderManager::DestroyVertexInputDeclaration(const RenderHandle handle)
{
    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    if (index < static_cast<uint32_t>(shaderVid_.rhr.size())) {
        shaderVid_.rhr[index] = {};
        shaderVid_.data[index] = {};

        auto eraseIndexData = [](auto& mapStore, const uint32_t index) {
            if (auto const pos = std::find_if(
                mapStore.begin(), mapStore.end(), [index](auto const& element) { return element.second == index; });
                pos != mapStore.end()) {
                mapStore.erase(pos);
            }
        };
        eraseIndexData(shaderVid_.nameToIndex, index);
        eraseIndexData(shaderVid_.shaderToIndex, index);
    }
}

vector<RenderHandleReference> ShaderManager::GetShaders(
    const RenderHandleReference& handle, const ShaderStageFlags shaderStageFlags) const
{
    vector<RenderHandleReference> shaders;
    if ((shaderStageFlags &
            (CORE_SHADER_STAGE_VERTEX_BIT | CORE_SHADER_STAGE_FRAGMENT_BIT | CORE_SHADER_STAGE_COMPUTE_BIT)) == 0) {
        return shaders;
    }
    const RenderHandleType handleType = handle.GetHandleType();
    const uint32_t handleIndex = RenderHandleUtil::GetIndexPart(handle.GetHandle());
    if (handleType == RenderHandleType::GRAPHICS_STATE) {
#if (RENDER_VALIDATION_ENABLED == 1)
        PLUGIN_LOG_W("RENDER_VALIDATION: GetShaders with graphics state handle not supported");
#endif
    } else if ((handleType == RenderHandleType::PIPELINE_LAYOUT) ||
               (handleType == RenderHandleType::VERTEX_INPUT_DECLARATION)) {
        if (shaderStageFlags & ShaderStageFlagBits::CORE_SHADER_STAGE_COMPUTE_BIT) {
            for (const auto& ref : computeShaderMappings_.clientData) {
                if (ref.pipelineLayoutIndex == handleIndex) {
                    shaders.push_back(ref.rhr);
                }
            }
        }
        if (shaderStageFlags & ShaderStageFlagBits::CORE_SHADER_STAGE_ALL_GRAPHICS) {
            for (const auto& ref : shaderMappings_.clientData) {
                if (ref.vertexInputDeclarationIndex == handleIndex) {
                    shaders.push_back(ref.rhr);
                }
            }
        }
    }
    return shaders;
}

vector<RenderHandle> ShaderManager::GetShaders(
    const RenderHandle& handle, const ShaderStageFlags shaderStageFlags) const
{
    vector<RenderHandle> shaders;
    if ((shaderStageFlags &
            (CORE_SHADER_STAGE_VERTEX_BIT | CORE_SHADER_STAGE_FRAGMENT_BIT | CORE_SHADER_STAGE_COMPUTE_BIT)) == 0) {
        return shaders;
    }
    const RenderHandleType handleType = RenderHandleUtil::GetHandleType(handle);
    const uint32_t handleIndex = RenderHandleUtil::GetIndexPart(handle);
    if (handleType == RenderHandleType::GRAPHICS_STATE) {
#if (RENDER_VALIDATION_ENABLED == 1)
        PLUGIN_LOG_W("RENDER_VALIDATION: GetShaders with graphics state handle not supported");
#endif
    } else if ((handleType == RenderHandleType::PIPELINE_LAYOUT) ||
               (handleType == RenderHandleType::VERTEX_INPUT_DECLARATION)) {
        if (shaderStageFlags & ShaderStageFlagBits::CORE_SHADER_STAGE_COMPUTE_BIT) {
            for (const auto& ref : computeShaderMappings_.clientData) {
                if (ref.pipelineLayoutIndex == handleIndex) {
                    shaders.push_back(ref.rhr.GetHandle());
                }
            }
        }
        if (shaderStageFlags & ShaderStageFlagBits::CORE_SHADER_STAGE_ALL_GRAPHICS) {
            for (const auto& ref : shaderMappings_.clientData) {
                if (ref.vertexInputDeclarationIndex == handleIndex) {
                    shaders.push_back(ref.rhr.GetHandle());
                }
            }
        }
    }
    return shaders;
}

vector<RenderHandleReference> ShaderManager::GetShaders() const
{
    vector<RenderHandleReference> shaders;
    shaders.reserve(computeShaderMappings_.clientData.size() + shaderMappings_.clientData.size());
    for (const auto& ref : computeShaderMappings_.clientData) {
        if (ref.rhr) {
            shaders.push_back(ref.rhr);
        }
    }
    for (const auto& ref : shaderMappings_.clientData) {
        if (ref.rhr) {
            shaders.push_back(ref.rhr);
        }
    }
    return shaders;
}

vector<RenderHandleReference> ShaderManager::GetGraphicsStates() const
{
    vector<RenderHandleReference> states;
    states.reserve(graphicsStates_.rhr.size());
    for (const auto& ref : graphicsStates_.rhr) {
        if (ref) {
            states.push_back(ref);
        }
    }
    return states;
}

vector<RenderHandleReference> ShaderManager::GetPipelineLayouts() const
{
    vector<RenderHandleReference> pls;
    pls.reserve(pl_.rhr.size());
    for (const auto& ref : pl_.rhr) {
        if (ref) {
            pls.push_back(ref);
        }
    }
    return pls;
}

vector<RenderHandleReference> ShaderManager::GetVertexInputDeclarations() const
{
    vector<RenderHandleReference> vids;
    vids.reserve(shaderVid_.rhr.size());
    for (const auto& ref : shaderVid_.rhr) {
        if (ref) {
            vids.push_back(ref);
        }
    }
    return vids;
}

IShaderManager::IdDesc ShaderManager::GetShaderIdDesc(const RenderHandle handle) const
{
    PLUGIN_ASSERT(computeShaderMappings_.clientData.size() == computeShaderMappings_.nameData.size());
    PLUGIN_ASSERT(shaderMappings_.clientData.size() == shaderMappings_.nameData.size());
    const RenderHandleType handleType = RenderHandleUtil::GetHandleType(handle);
    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    IdDesc desc;
    if ((handleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) &&
        (index < static_cast<uint32_t>(computeShaderMappings_.clientData.size()))) {
        const auto& cdRef = computeShaderMappings_.clientData[index];
        const auto& nameRef = computeShaderMappings_.nameData[index];
        desc.frameIndex = cdRef.frameIndex;
        desc.renderSlot = GetRenderSlotName(cdRef.renderSlotId);
        desc.category = GetCategoryName(cdRef.categoryId);
        desc.displayName = nameRef.displayName;
        desc.path = nameRef.path;
        desc.variant = nameRef.variantName;
    } else if ((handleType == RenderHandleType::SHADER_STATE_OBJECT) &&
               (index < static_cast<uint32_t>(shaderMappings_.clientData.size()))) {
        const auto& cdRef = shaderMappings_.clientData[index];
        const auto& nameRef = shaderMappings_.nameData[index];
        desc.frameIndex = cdRef.frameIndex;
        desc.renderSlot = GetRenderSlotName(cdRef.renderSlotId);
        desc.category = GetCategoryName(cdRef.categoryId);
        desc.displayName = nameRef.displayName;
        desc.path = nameRef.path;
        desc.variant = nameRef.variantName;
    }
    return desc;
}

uint64_t ShaderManager::GetShaderFrameIndex(const RenderHandle handle) const
{
    const RenderHandleType handleType = RenderHandleUtil::GetHandleType(handle);
    const uint32_t index = RenderHandleUtil::GetIndexPart(handle);
    uint64_t frameIndex = 0;
    if ((handleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) &&
        (index < static_cast<uint32_t>(computeShaderMappings_.clientData.size()))) {
        frameIndex = computeShaderMappings_.clientData[index].frameIndex;
    } else if ((handleType == RenderHandleType::SHADER_STATE_OBJECT) &&
               (index < static_cast<uint32_t>(shaderMappings_.clientData.size()))) {
        frameIndex = shaderMappings_.clientData[index].frameIndex;
    }
    return frameIndex;
}

IShaderManager::IdDesc ShaderManager::GetIdDesc(const RenderHandleReference& handle) const
{
    auto GetIdDesc = [](const auto& nameToIndex, const auto handleIndex) {
        IdDesc desc;
        for (const auto& ref : nameToIndex) {
            if (ref.second == handleIndex) {
                desc.path = ref.first;
            }
        }
        return desc;
    };
    const RenderHandle rawHandle = handle.GetHandle();
    const RenderHandleType handleType = RenderHandleUtil::GetHandleType(rawHandle);
    const uint32_t handleIndex = RenderHandleUtil::GetIndexPart(rawHandle);
    IdDesc desc;
    if ((handleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) ||
        (handleType == RenderHandleType::SHADER_STATE_OBJECT)) {
        desc = GetShaderIdDesc(rawHandle);
    } else if ((handleType == RenderHandleType::GRAPHICS_STATE) && (handleIndex < graphicsStates_.rhr.size())) {
        desc = GetIdDesc(graphicsStates_.nameToIndex, handleIndex);
    } else if ((handleType == RenderHandleType::PIPELINE_LAYOUT) && (handleIndex < pl_.rhr.size())) {
        desc = GetIdDesc(pl_.nameToIndex, handleIndex);
    } else if ((handleType == RenderHandleType::VERTEX_INPUT_DECLARATION) && (handleIndex < shaderVid_.rhr.size())) {
        desc = GetIdDesc(shaderVid_.nameToIndex, handleIndex);
    }
    return desc;
}

uint64_t ShaderManager::GetFrameIndex(const RenderHandleReference& handle) const
{
    const RenderHandle rawHandle = handle.GetHandle();
    const RenderHandleType handleType = RenderHandleUtil::GetHandleType(rawHandle);
    const uint32_t handleIndex = RenderHandleUtil::GetIndexPart(rawHandle);
    uint64_t frameIndex = 0;
    if ((handleType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT) ||
        (handleType == RenderHandleType::SHADER_STATE_OBJECT)) {
        frameIndex = GetShaderFrameIndex(rawHandle);
    } else if ((handleType == RenderHandleType::GRAPHICS_STATE) && (handleIndex < graphicsStates_.rhr.size())) {
        frameIndex = 0;
    } else if ((handleType == RenderHandleType::PIPELINE_LAYOUT) && (handleIndex < pl_.rhr.size())) {
        frameIndex = 0;
    } else if ((handleType == RenderHandleType::VERTEX_INPUT_DECLARATION) && (handleIndex < shaderVid_.rhr.size())) {
        frameIndex = 0;
    }
    return frameIndex;
}

IShaderPipelineBinder::Ptr ShaderManager::CreateShaderPipelineBinder(
    const RenderHandleReference& handle, const PipelineLayout& pipelineLayout) const
{
    const RenderHandleType type = handle.GetHandleType();
    if (handle &&
        ((type == RenderHandleType::SHADER_STATE_OBJECT) || (type == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT))) {
        return IShaderPipelineBinder::Ptr { new ShaderPipelineBinder((IShaderManager&)*this, handle, pipelineLayout) };
    }
    return nullptr;
}

IShaderPipelineBinder::Ptr ShaderManager::CreateShaderPipelineBinder(
    const RenderHandleReference& handle, const RenderHandleReference& plHandle) const
{
    RenderHandleReference finalPlHandle = plHandle;
    if (!finalPlHandle) {
        finalPlHandle = GetPipelineLayoutHandleByShaderHandle(handle.GetHandle());
        if (!finalPlHandle) {
            finalPlHandle = GetReflectionPipelineLayoutHandle(handle.GetHandle());
        }
    }
    return CreateShaderPipelineBinder(handle, GetPipelineLayout(finalPlHandle));
}

IShaderPipelineBinder::Ptr ShaderManager::CreateShaderPipelineBinder(const RenderHandleReference& handle) const
{
    return CreateShaderPipelineBinder(handle, RenderHandleReference {});
}

ShaderManager::CompatibilityFlags ShaderManager::GetCompatibilityFlags(
    const RenderHandle& lhs, const RenderHandle& rhs) const
{
    const RenderHandleType lType = RenderHandleUtil::GetHandleType(lhs);
    const RenderHandleType rType = RenderHandleUtil::GetHandleType(rhs);
    CompatibilityFlags flags = 0;
    // NOTE: only same types supported at the moment
    if (lType == rType) {
        if (lType == RenderHandleType::PIPELINE_LAYOUT) {
            const PipelineLayout lpl = GetPipelineLayout(lhs);
            const PipelineLayout rpl = GetPipelineLayout(rhs);
            flags = GetPipelineLayoutCompatibilityFlags(lpl, rpl);
        } else if ((lType == RenderHandleType::SHADER_STATE_OBJECT) ||
                   (lType == RenderHandleType::COMPUTE_SHADER_STATE_OBJECT)) {
            // first check that given pipeline layout is valid to own reflection
            const RenderHandle shaderPlHandle = GetPipelineLayoutHandleByShaderHandle(rhs).GetHandle();
            if (RenderHandleUtil::IsValid(shaderPlHandle)) {
                const PipelineLayout shaderPl = GetPipelineLayout(shaderPlHandle);
                const PipelineLayout rpl = GetReflectionPipelineLayoutRef(rhs);
                if (rpl.descriptorSetCount > 0) {
                    flags = GetPipelineLayoutCompatibilityFlags(rpl, shaderPl);
                }
            } else {
                // some shaders do not specify actual pipeline layout, only shader reflection pipeline layout
                flags = 1u;
            }
            // then, compare to lhs with rhs reflection
            if (flags != 0) {
                const RenderHandle lShaderPlHandle = GetPipelineLayoutHandleByShaderHandle(lhs).GetHandle();
                const PipelineLayout lpl = RenderHandleUtil::IsValid(lShaderPlHandle)
                                               ? GetPipelineLayout(lShaderPlHandle)
                                               : GetReflectionPipelineLayoutRef(lhs);
                flags = GetPipelineLayoutCompatibilityFlags(lpl, GetReflectionPipelineLayoutRef(rhs));
            }
        }
    }
    return flags;
}

ShaderManager::CompatibilityFlags ShaderManager::GetCompatibilityFlags(
    const RenderHandleReference& lhs, const RenderHandleReference& rhs) const
{
    if (lhs && rhs) {
        return GetCompatibilityFlags(lhs.GetHandle(), rhs.GetHandle());
    } else {
        return CompatibilityFlags { 0 };
    }
}

GraphicsStateFlags ShaderManager::GetForcedGraphicsStateFlags(const RenderHandle& handle) const
{
    if (!RenderHandleUtil::IsValid(handle)) {
        return 0U; // early out
    }

    const RenderHandleType type = RenderHandleUtil::GetHandleType(handle);
    const uint32_t arrayIndex = RenderHandleUtil::GetIndexPart(handle);
    GraphicsStateFlags flags { 0u };

    uint32_t graphicsStateIndex = ~0u;
    if (type == RenderHandleType::GRAPHICS_STATE) {
        graphicsStateIndex = arrayIndex;
    } else if ((type == RenderHandleType::SHADER_STATE_OBJECT) &&
               (arrayIndex < static_cast<uint32_t>(shaderMappings_.clientData.size()))) {
        graphicsStateIndex = shaderMappings_.clientData[arrayIndex].graphicsStateIndex;
    }

    if (graphicsStateIndex < static_cast<uint32_t>(graphicsStates_.graphicsStates.size())) {
        flags = graphicsStates_.data[arrayIndex].stateFlags;
    }
    return flags;
}

GraphicsStateFlags ShaderManager::GetForcedGraphicsStateFlags(const RenderHandleReference& handle) const
{
    return GetForcedGraphicsStateFlags(handle.GetHandle());
}

GraphicsStateFlags ShaderManager::GetForcedGraphicsStateFlags(const uint32_t renderSlotId) const
{
    if (renderSlotId < static_cast<uint32_t>(renderSlotIds_.data.size())) {
        return GetForcedGraphicsStateFlags(renderSlotIds_.data[renderSlotId].graphicsState.GetHandle());
    }
    return 0u;
}

void ShaderManager::SetFileManager(IFileManager& fileMgr)
{
    fileMgr_ = &fileMgr;
    shaderLoader_ = make_unique<ShaderLoader>(*fileMgr_, *this, device_.GetBackendType());
}

constexpr uint8_t REFLECTION_TAG[] = { 'r', 'f', 'l', 0 };
struct ReflectionHeader {
    uint8_t tag[sizeof(REFLECTION_TAG)];
    uint16_t type;
    uint16_t offsetPushConstants;
    uint16_t offsetSpecializationConstants;
    uint16_t offsetDescriptorSets;
    uint16_t offsetInputs;
    uint16_t offsetLocalSize;
};

bool ShaderReflectionData::IsValid() const
{
    if (reflectionData.size() < sizeof(ReflectionHeader)) {
        return false;
    }
    const ReflectionHeader& header = *reinterpret_cast<const ReflectionHeader*>(reflectionData.data());
    return memcmp(header.tag, REFLECTION_TAG, sizeof(REFLECTION_TAG)) == 0;
}

ShaderStageFlags ShaderReflectionData::GetStageFlags() const
{
    ShaderStageFlags flags;
    const ReflectionHeader& header = *reinterpret_cast<const ReflectionHeader*>(reflectionData.data());
    flags = header.type;
    return flags;
}

PipelineLayout ShaderReflectionData::GetPipelineLayout() const
{
    PipelineLayout pipelineLayout;
    const ReflectionHeader& header = *reinterpret_cast<const ReflectionHeader*>(reflectionData.data());
    if (header.offsetPushConstants && header.offsetPushConstants < reflectionData.size()) {
        auto ptr = reflectionData.data() + header.offsetPushConstants;
        const auto constants = *ptr;
        if (constants) {
            pipelineLayout.pushConstant.shaderStageFlags = header.type;
            pipelineLayout.pushConstant.byteSize = static_cast<uint32_t>(*(ptr + 1) | (*(ptr + 2) << 8));
        }
    }
    if (header.offsetDescriptorSets && header.offsetDescriptorSets < reflectionData.size()) {
        auto ptr = reflectionData.data() + header.offsetDescriptorSets;
        pipelineLayout.descriptorSetCount = static_cast<uint32_t>(*(ptr) | (*(ptr + 1) << 8));
        ptr += 2;
        for (auto i = 0u; i < pipelineLayout.descriptorSetCount; ++i) {
            // write to correct set location
            const uint32_t set = static_cast<uint32_t>(*ptr | (*(ptr + 1) << 8));
            PLUGIN_ASSERT(set < PipelineLayoutConstants::MAX_DESCRIPTOR_SET_COUNT);
            auto& layout = pipelineLayout.descriptorSetLayouts[set];
            layout.set = set;
            ptr += 2;
            const auto bindings = static_cast<uint32_t>(*ptr | (*(ptr + 1) << 8));
            ptr += 2;
            for (auto j = 0u; j < bindings; ++j) {
                DescriptorSetLayoutBinding binding;
                binding.binding = static_cast<uint32_t>(*ptr | (*(ptr + 1) << 8));
                ptr += 2;
                binding.descriptorType = static_cast<DescriptorType>(*ptr | (*(ptr + 1) << 8));
                if ((binding.descriptorType > DescriptorType::CORE_DESCRIPTOR_TYPE_INPUT_ATTACHMENT) &&
                    (binding.descriptorType ==
                        (DescriptorType::CORE_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE & 0xffff))) {
                    binding.descriptorType = DescriptorType::CORE_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE;
                }
                ptr += 2;
                binding.descriptorCount = static_cast<uint32_t>(*ptr | (*(ptr + 1) << 8));
                ptr += 2;
                binding.shaderStageFlags = header.type;
                layout.bindings.push_back(binding);
            }
        }
    }
    return pipelineLayout;
}

vector<ShaderSpecialization::Constant> ShaderReflectionData::GetSpecializationConstants() const
{
    vector<ShaderSpecialization::Constant> constants;
    const ReflectionHeader& header = *reinterpret_cast<const ReflectionHeader*>(reflectionData.data());
    if (header.offsetSpecializationConstants && header.offsetSpecializationConstants < reflectionData.size()) {
        auto ptr = reflectionData.data() + header.offsetSpecializationConstants;
        const auto size = *ptr | *(ptr + 1) << 8 | *(ptr + 2) << 16 | *(ptr + 3) << 24;
        ptr += 4;
        for (auto i = 0; i < size; ++i) {
            ShaderSpecialization::Constant constant;
            constant.shaderStage = header.type;
            constant.id = static_cast<uint32_t>(*ptr | *(ptr + 1) << 8 | *(ptr + 2) << 16 | *(ptr + 3) << 24);
            ptr += 4;
            constant.type = static_cast<ShaderSpecialization::Constant::Type>(
                *ptr | *(ptr + 1) << 8 | *(ptr + 2) << 16 | *(ptr + 3) << 24);
            ptr += 4;
            constant.offset = 0;
            constants.push_back(constant);
        }
    }
    return constants;
}

vector<VertexInputDeclaration::VertexInputAttributeDescription> ShaderReflectionData::GetInputDescriptions() const
{
    vector<VertexInputDeclaration::VertexInputAttributeDescription> inputs;
    const ReflectionHeader& header = *reinterpret_cast<const ReflectionHeader*>(reflectionData.data());
    if (header.offsetInputs && header.offsetInputs < reflectionData.size()) {
        auto ptr = reflectionData.data() + header.offsetInputs;
        const auto size = *(ptr) | (*(ptr + 1) << 8);
        ptr += 2;
        for (auto i = 0; i < size; ++i) {
            VertexInputDeclaration::VertexInputAttributeDescription desc;
            desc.location = static_cast<uint32_t>(*(ptr) | (*(ptr + 1) << 8));
            ptr += 2;
            desc.binding = desc.location;
            desc.format = static_cast<Format>(*(ptr) | (*(ptr + 1) << 8));
            ptr += 2;
            desc.offset = 0;
            inputs.push_back(desc);
        }
    }
    return inputs;
}

Math::UVec3 ShaderReflectionData::GetLocalSize() const
{
    Math::UVec3 sizes;
    const ReflectionHeader& header = *reinterpret_cast<const ReflectionHeader*>(reflectionData.data());
    if (header.offsetLocalSize && header.offsetLocalSize < reflectionData.size()) {
        auto ptr = reflectionData.data() + header.offsetLocalSize;
        sizes.x = static_cast<uint32_t>(*ptr | (*(ptr + 1) << 8) | (*(ptr + 2)) << 16 | (*(ptr + 3)) << 24);
        ptr += 4;
        sizes.y = static_cast<uint32_t>(*ptr | (*(ptr + 1) << 8) | (*(ptr + 2)) << 16 | (*(ptr + 3)) << 24);
        ptr += 4;
        sizes.z = static_cast<uint32_t>(*ptr | (*(ptr + 1) << 8) | (*(ptr + 2)) << 16 | (*(ptr + 3)) << 24);
    }
    return sizes;
}

const uint8_t* ShaderReflectionData::GetPushConstants() const
{
    const uint8_t* ptr = nullptr;
    const ReflectionHeader& header = *reinterpret_cast<const ReflectionHeader*>(reflectionData.data());
    if (header.offsetPushConstants && header.offsetPushConstants < reflectionData.size()) {
        const auto constants = *(reflectionData.data() + header.offsetPushConstants);
        if (constants) {
            // number of constants is uint8 and the size of the constant is uint16
            ptr = reflectionData.data() + header.offsetPushConstants + sizeof(uint8_t) + sizeof(uint16_t);
        }
    }
    return ptr;
}

RenderNodeShaderManager::RenderNodeShaderManager(const ShaderManager& shaderMgr) : shaderMgr_(shaderMgr) {}

RenderHandle RenderNodeShaderManager::GetShaderHandle(const string_view path) const
{
    return shaderMgr_.GetShaderHandle(path).GetHandle();
}

RenderHandle RenderNodeShaderManager::GetShaderHandle(const string_view path, const string_view variantName) const
{
    return shaderMgr_.GetShaderHandle(path, variantName).GetHandle();
}

RenderHandle RenderNodeShaderManager::GetShaderHandle(const RenderHandle& handle, const uint32_t renderSlotId) const
{
    return shaderMgr_.GetShaderHandle(handle, renderSlotId).GetHandle();
}

vector<RenderHandle> RenderNodeShaderManager::GetShaders(const uint32_t renderSlotId) const
{
    return shaderMgr_.GetShaderRawHandles(renderSlotId);
}

RenderHandle RenderNodeShaderManager::GetGraphicsStateHandle(const string_view path) const
{
    return shaderMgr_.GetGraphicsStateHandle(path).GetHandle();
}

RenderHandle RenderNodeShaderManager::GetGraphicsStateHandle(
    const string_view path, const string_view variantName) const
{
    return shaderMgr_.GetGraphicsStateHandle(path, variantName).GetHandle();
}

RenderHandle RenderNodeShaderManager::GetGraphicsStateHandle(
    const RenderHandle& handle, const uint32_t renderSlotId) const
{
    return shaderMgr_.GetGraphicsStateHandle(handle, renderSlotId).GetHandle();
}

RenderHandle RenderNodeShaderManager::GetGraphicsStateHandleByHash(const uint64_t hash) const
{
    return shaderMgr_.GetGraphicsStateHandleByHash(hash).GetHandle();
}

RenderHandle RenderNodeShaderManager::GetGraphicsStateHandleByShaderHandle(const RenderHandle& handle) const
{
    return shaderMgr_.GetGraphicsStateHandleByShaderHandle(handle).GetHandle();
}

const GraphicsState& RenderNodeShaderManager::GetGraphicsState(const RenderHandle& handle) const
{
    return shaderMgr_.GetGraphicsStateRef(handle);
}

uint32_t RenderNodeShaderManager::GetRenderSlotId(const string_view renderSlot) const
{
    return shaderMgr_.GetRenderSlotId(renderSlot);
}

uint32_t RenderNodeShaderManager::GetRenderSlotId(const RenderHandle& handle) const
{
    return shaderMgr_.GetRenderSlotId(handle);
}

IShaderManager::RenderSlotData RenderNodeShaderManager::GetRenderSlotData(const uint32_t renderSlotId) const
{
    return shaderMgr_.GetRenderSlotData(renderSlotId);
}

RenderHandle RenderNodeShaderManager::GetVertexInputDeclarationHandleByShaderHandle(const RenderHandle& handle) const
{
    return shaderMgr_.GetVertexInputDeclarationHandleByShaderHandle(handle).GetHandle();
}

RenderHandle RenderNodeShaderManager::GetVertexInputDeclarationHandle(const string_view path) const
{
    return shaderMgr_.GetVertexInputDeclarationHandle(path).GetHandle();
}

VertexInputDeclarationView RenderNodeShaderManager::GetVertexInputDeclarationView(const RenderHandle& handle) const
{
    return shaderMgr_.GetVertexInputDeclarationView(handle);
}

RenderHandle RenderNodeShaderManager::GetPipelineLayoutHandleByShaderHandle(const RenderHandle& handle) const
{
    return shaderMgr_.GetPipelineLayoutHandleByShaderHandle(handle).GetHandle();
}

const PipelineLayout& RenderNodeShaderManager::GetPipelineLayout(const RenderHandle& handle) const
{
    return shaderMgr_.GetPipelineLayoutRef(handle);
}

RenderHandle RenderNodeShaderManager::GetPipelineLayoutHandle(const string_view path) const
{
    return shaderMgr_.GetPipelineLayoutHandle(path).GetHandle();
}

RenderHandle RenderNodeShaderManager::GetReflectionPipelineLayoutHandle(const RenderHandle& handle) const
{
    return shaderMgr_.GetReflectionPipelineLayoutHandle(handle).GetHandle();
}

const PipelineLayout& RenderNodeShaderManager::GetReflectionPipelineLayout(const RenderHandle& handle) const
{
    return shaderMgr_.GetReflectionPipelineLayoutRef(handle);
}

ShaderSpecializationConstantView RenderNodeShaderManager::GetReflectionSpecialization(const RenderHandle& handle) const
{
    return shaderMgr_.GetReflectionSpecialization(handle);
}

VertexInputDeclarationView RenderNodeShaderManager::GetReflectionVertexInputDeclaration(
    const RenderHandle& handle) const
{
    return shaderMgr_.GetReflectionVertexInputDeclaration(handle);
}

ShaderThreadGroup RenderNodeShaderManager::GetReflectionThreadGroupSize(const RenderHandle& handle) const
{
    return shaderMgr_.GetReflectionThreadGroupSize(handle);
}

uint64_t RenderNodeShaderManager::HashGraphicsState(const GraphicsState& graphicsState) const
{
    return shaderMgr_.HashGraphicsState(graphicsState);
}

bool RenderNodeShaderManager::IsValid(const RenderHandle& handle) const
{
    return RenderHandleUtil::IsValid(handle);
}

bool RenderNodeShaderManager::IsComputeShader(const RenderHandle& handle) const
{
    return IsComputeShaderFunc(handle);
}

bool RenderNodeShaderManager::IsShader(const RenderHandle& handle) const
{
    return IsShaderFunc(handle);
}

vector<RenderHandle> RenderNodeShaderManager::GetShaders(
    const RenderHandle& handle, const ShaderStageFlags shaderStageFlags) const
{
    return shaderMgr_.GetShaders(handle, shaderStageFlags);
}

IShaderManager::CompatibilityFlags RenderNodeShaderManager::GetCompatibilityFlags(
    const RenderHandle& lhs, const RenderHandle& rhs) const
{
    return shaderMgr_.GetCompatibilityFlags(lhs, rhs);
}

GraphicsStateFlags RenderNodeShaderManager::GetForcedGraphicsStateFlags(const RenderHandle& handle) const
{
    return shaderMgr_.GetForcedGraphicsStateFlags(handle);
}

GraphicsStateFlags RenderNodeShaderManager::GetForcedGraphicsStateFlags(const uint32_t renderSlotId) const
{
    return shaderMgr_.GetForcedGraphicsStateFlags(renderSlotId);
}
RENDER_END_NAMESPACE()