text: /******************************************************************************
   * The MIT License (MIT)
   *
   * Copyright (c) 2019 Baldur Karlsson
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   ******************************************************************************/
  
  #include "vk_resources.h"
  
  ImageSubresourceRange ImageInfo::FullRange() const
  {
    return ImageSubresourceRange(
        /* aspectMask = */ Aspects(),
        /* baseMipLevel = */ 0u,
        /* levelCount = */ (uint32_t)levelCount,
        /* baseArrayLayer = */ 0u,
        /* layerCount = */ (uint32_t)layerCount,
        /* baseDepthSlice = */ 0u,
        /* sliceCount = */ extent.depth);
  }
  
  void ImageSubresourceState::Update(const ImageSubresourceState &other, FrameRefCompFunc compose)
  {
    if(oldQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
      oldQueueFamilyIndex = other.oldQueueFamilyIndex;
  
    if(other.newQueueFamilyIndex != VK_QUEUE_FAMILY_IGNORED)
      newQueueFamilyIndex = other.newQueueFamilyIndex;
  
    if(oldLayout == UNKNOWN_PREV_IMG_LAYOUT)
      oldLayout = other.oldLayout;
  
    if(other.newLayout != UNKNOWN_PREV_IMG_LAYOUT)
      newLayout = other.newLayout;
  
    refType = compose(refType, other.refType);
  }
  
  bool ImageSubresourceState::Update(const ImageSubresourceState &other,
                                     ImageSubresourceState &result, FrameRefCompFunc compose) const
  {
    result = *this;
    result.Update(other, compose);
  
    return result != *this;
  }
  
  template <typename Map, typename Pair>
  typename ImageSubresourceMap::SubresourceRangeIterTemplate<Map, Pair>
      &ImageSubresourceMap::SubresourceRangeIterTemplate<Map, Pair>::operator++()
  {
    if(!IsValid())
      return *this;
    FixSubRange();
  
    ++m_slice;
    if(IsDepthSplit(m_splitFlags) && m_slice < m_range.baseDepthSlice + m_range.sliceCount)
    {
      m_value.m_range.baseDepthSlice = m_slice;
      return *this;
    }
    m_value.m_range.baseDepthSlice = m_slice = m_range.baseDepthSlice;
  
    ++m_layer;
    if(AreLayersSplit(m_splitFlags) && m_layer < m_range.baseArrayLayer + m_range.layerCount)
    {
      m_value.m_range.baseArrayLayer = m_layer;
      return *this;
    }
    m_value.m_range.baseArrayLayer = m_layer = m_range.baseArrayLayer;
  
    ++m_level;
    if(AreLevelsSplit(m_splitFlags) && m_level < m_range.baseMipLevel + m_range.levelCount)
    {
      m_value.m_range.baseMipLevel = m_level;
      return *this;
    }
    m_value.m_range.baseMipLevel = m_level = m_range.baseMipLevel;
  
    if(AreAspectsSplit(m_splitFlags))
    {
      auto aspectIt = ImageAspectFlagIter(m_map->GetImageInfo().Aspects(),
                                          (VkImageAspectFlagBits)m_value.m_range.aspectMask);
      while(true)
      {
        ++m_aspectIndex;
        ++aspectIt;
        if(aspectIt == ImageAspectFlagIter::end())
        {
          break;
        }
        else if(m_range.aspectMask & *aspectIt)
        {
          m_value.m_range.aspectMask = *aspectIt;
          return *this;
        }
      }
    }
  
    // iterator is at the end.
    // make `m_aspectIndex` out of range to mark this.
    m_aspectIndex = m_map->m_aspectCount;
    return *this;
  }
  template
      typename ImageSubresourceMap::SubresourceRangeIterTemplate<ImageSubresourceMap,
                                                                 ImageSubresourceMap::SubresourcePairRef>
          &ImageSubresourceMap::SubresourceRangeIterTemplate<
              ImageSubresourceMap, ImageSubresourceMap::SubresourcePairRef>::operator++();
  template typename ImageSubresourceMap::SubresourceRangeIterTemplate<
      const ImageSubresourceMap, ImageSubresourceMap::ConstSubresourcePairRef>
      &ImageSubresourceMap::SubresourceRangeIterTemplate<
          const ImageSubresourceMap, ImageSubresourceMap::ConstSubresourcePairRef>::operator++();
  
  void ImageSubresourceMap::Split(bool splitAspects, bool splitLevels, bool splitLayers, bool splitDepth)
  {
    uint16_t newFlags = m_flags;
    if(splitAspects)
      newFlags |= (uint16_t)FlagBits::AreAspectsSplit;
    else
      splitAspects = AreAspectsSplit();
  
    if(splitLevels)
      newFlags |= (uint16_t)FlagBits::AreLevelsSplit;
    else
      splitLevels = AreLevelsSplit();
  
    if(splitLayers)
      newFlags |= (uint16_t)FlagBits::AreLayersSplit;
    else
      splitLayers = AreLayersSplit();
  
    if(splitDepth)
      newFlags |= (uint16_t)FlagBits::IsDepthSplit;
    else
      splitDepth = IsDepthSplit();
  
    if(newFlags == m_flags)
      // not splitting anything new
      return;
  
    uint32_t oldSplitAspectCount = AreAspectsSplit() ? m_aspectCount : 1;
    uint32_t newSplitAspectCount = splitAspects ? m_aspectCount : oldSplitAspectCount;
  
    uint32_t oldSplitLevelCount = AreLevelsSplit() ? GetImageInfo().levelCount : 1;
    uint32_t newSplitLevelCount = splitLevels ? GetImageInfo().levelCount : oldSplitLevelCount;
  
    uint32_t oldSplitLayerCount = AreLayersSplit() ? GetImageInfo().layerCount : 1;
    uint32_t newSplitLayerCount = splitLayers ? GetImageInfo().layerCount : oldSplitLayerCount;
  
    uint32_t oldSplitSliceCount = IsDepthSplit() ? GetImageInfo().extent.depth : 1;
    uint32_t newSplitSliceCount = splitDepth ? GetImageInfo().extent.depth : oldSplitSliceCount;
  
    uint32_t oldSize = (uint32_t)m_values.size();
    RDCASSERT(oldSize > 0);
  
    uint32_t newSize =
        newSplitAspectCount * newSplitLevelCount * newSplitLayerCount * newSplitSliceCount;
    RDCASSERT(newSize > oldSize);
  
    m_values.resize(newSize);
  
    uint32_t newAspectIndex = newSplitAspectCount - 1;
    uint32_t oldAspectIndex = AreAspectsSplit() ? newAspectIndex : 0;
    uint32_t newLevel = newSplitLevelCount - 1;
    uint32_t oldLevel = AreLevelsSplit() ? newLevel : 0;
    uint32_t newLayer = newSplitLayerCount - 1;
    uint32_t oldLayer = AreLayersSplit() ? newLayer : 0;
    uint32_t newSlice = newSplitSliceCount - 1;
    uint32_t oldSlice = IsDepthSplit() ? newSlice : 0;
    uint32_t newIndex = newSize - 1;
    while(true)
    {
      uint32_t oldIndex =
          ((oldAspectIndex * oldSplitLevelCount + oldLevel) * oldSplitLayerCount + oldLayer) *
              oldSplitSliceCount +
          oldSlice;
      m_values[newIndex] = m_values[oldIndex];
  
      if(newIndex == 0)
      {
        RDCASSERT(oldIndex == 0);
        break;
      }
      --newIndex;
  
      if(newSlice > 0)
      {
        --newSlice;
        oldSlice = IsDepthSplit() ? newSlice : 0;
        continue;
      }
      newSlice = newSplitSliceCount - 1;
      oldSlice = oldSplitSliceCount - 1;
  
      if(newLayer > 0)
      {
        --newLayer;
        oldLayer = AreLayersSplit() ? newLayer : 0;
        continue;
      }
      newLayer = newSplitLayerCount - 1;
      oldLayer = oldSplitLayerCount - 1;
  
      if(newLevel > 0)
      {
        --newLevel;
        oldLevel = AreLevelsSplit() ? newLevel : 0;
        continue;
      }
      newLevel = newSplitLevelCount - 1;
      oldLevel = oldSplitLevelCount - 1;
  
      if(newAspectIndex > 0)
      {
        --newAspectIndex;
        oldAspectIndex = AreAspectsSplit() ? newAspectIndex : 0;
        continue;
      }
      RDCERR("Too many subresources in ImageSubresourceMap::Split");
      break;
    }
  
    m_flags = newFlags;
  }
  
  void ImageSubresourceMap::Unsplit(bool unsplitAspects, bool unsplitLevels, bool unsplitLayers,
                                    bool unsplitDepth)
  {
    uint16_t newFlags = m_flags;
    if(unsplitAspects)
      newFlags &= ~(uint16_t)FlagBits::AreAspectsSplit;
  
    if(unsplitLevels)
      newFlags &= ~(uint16_t)FlagBits::AreLevelsSplit;
  
    if(unsplitLayers)
      newFlags &= ~(uint16_t)FlagBits::AreLayersSplit;
  
    if(unsplitDepth)
      newFlags &= ~(uint16_t)FlagBits::IsDepthSplit;
  
    if(newFlags == m_flags)
      // not splitting anything new
      return;
  
    uint32_t oldSplitAspectCount = AreAspectsSplit() ? m_aspectCount : 1;
    uint32_t newSplitAspectCount = unsplitAspects ? 1 : oldSplitAspectCount;
  
    uint32_t oldSplitLevelCount = AreLevelsSplit() ? GetImageInfo().levelCount : 1;
    uint32_t newSplitLevelCount = unsplitLevels ? 1 : oldSplitLevelCount;
  
    uint32_t oldSplitLayerCount = AreLayersSplit() ? GetImageInfo().layerCount : 1;
    uint32_t newSplitLayerCount = unsplitLayers ? 1 : oldSplitLayerCount;
  
    uint32_t oldSplitSliceCount = IsDepthSplit() ? GetImageInfo().extent.depth : 1;
    uint32_t newSplitSliceCount = unsplitDepth ? 1 : oldSplitSliceCount;
  
    uint32_t oldSize = (uint32_t)m_values.size();
    RDCASSERT(oldSize > 0);
  
    uint32_t newSize =
        newSplitAspectCount * newSplitLevelCount * newSplitLayerCount * newSplitSliceCount;
    RDCASSERT(newSize < oldSize);
  
    rdcarray<ImageSubresourceState> newValues;
    newValues.resize(newSize);
  
    uint32_t aspectIndex = 0;
    uint32_t level = 0;
    uint32_t layer = 0;
    uint32_t slice = 0;
    uint32_t newIndex = 0;
  
    while(newIndex < newValues.size())
    {
      uint32_t oldIndex = ((aspectIndex * oldSplitLevelCount + level) * oldSplitLayerCount + layer) *
                              oldSplitSliceCount +
                          slice;
      newValues[newIndex] = m_values[oldIndex];
  
      ++newIndex;
  
      ++slice;
      if(slice < newSplitSliceCount)
        continue;
      slice = 0;
  
      ++layer;
      if(layer < newSplitLayerCount)
        continue;
      layer = 0;
  
      ++level;
      if(level < newSplitLevelCount)
        continue;
      level = 0;
  
      ++aspectIndex;
    }
  
    newValues.swap(m_values);
    m_flags = newFlags;
  }
  
  void ImageSubresourceMap::Unsplit()
  {
    if(m_values.size() == 1)
      return;
  
    uint32_t aspectCount = AreAspectsSplit() ? m_aspectCount : 1;
    uint32_t aspectIndex = 0;
    uint32_t levelCount = AreLevelsSplit() ? m_imageInfo.levelCount : 1;
    uint32_t level = 0;
    uint32_t layerCount = AreLayersSplit() ? m_imageInfo.layerCount : 1;
    uint32_t layer = 0;
    uint32_t sliceCount = IsDepthSplit() ? m_imageInfo.extent.depth : 1;
    uint32_t slice = 0;
    uint32_t index = 0;
  
    bool canUnsplitAspects = aspectCount > 1;
    bool canUnsplitLevels = levelCount > 1;
    bool canUnsplitLayers = layerCount > 1;
    bool canUnsplitDepth = sliceCount > 1;
  
    RDCASSERT(aspectCount * levelCount * layerCount * sliceCount == m_values.size());
  #define UNSPLIT_INDEX(ASPECT, LEVEL, LAYER, SLICE) \
    ((((ASPECT)*levelCount + (LEVEL)) * layerCount + (LAYER)) * sliceCount + (SLICE))
    while(index < m_values.size() &&
          (canUnsplitAspects || canUnsplitLevels || canUnsplitLayers || canUnsplitDepth))
    {
      if(canUnsplitAspects && aspectIndex > 0)
      {
        uint32_t index0 = UNSPLIT_INDEX(0, level, layer, slice);
        if(m_values[index] != m_values[index0])
          canUnsplitAspects = false;
      }
      if(canUnsplitLevels && level > 0)
      {
        uint32_t index0 = UNSPLIT_INDEX(aspectIndex, 0, layer, slice);
        if(m_values[index] != m_values[index0])
          canUnsplitLevels = false;
      }
      if(canUnsplitLayers && layer > 0)
      {
        uint32_t index0 = UNSPLIT_INDEX(aspectIndex, level, 0, slice);
        if(m_values[index] != m_values[index0])
          canUnsplitLayers = false;
      }
      if(canUnsplitDepth && slice > 0)
      {
        uint32_t index0 = UNSPLIT_INDEX(aspectIndex, level, layer, 0);
        if(m_values[index] != m_values[index0])
          canUnsplitDepth = false;
      }
  
      ++index;
  
      ++slice;
      if(slice < sliceCount)
        continue;
      slice = 0;
  
      ++layer;
      if(layer < layerCount)
        continue;
      layer = 0;
  
      ++level;
      if(level < levelCount)
        continue;
      level = 0;
  
      ++aspectIndex;
      if(aspectIndex >= aspectCount)
        break;
    }
  #undef UNSPLIT_INDEX
  
    Unsplit(canUnsplitAspects, canUnsplitLevels, canUnsplitLayers, canUnsplitDepth);
  }
  
  inline FrameRefType ImageSubresourceMap::Merge(const ImageSubresourceMap &other,
                                                 FrameRefCompFunc compose)
  {
    FrameRefType maxRefType = eFrameRef_None;
    bool didSplit = false;
    for(auto oIt = other.begin(); oIt != other.end(); ++oIt)
    {
      for(auto it = RangeBegin(oIt->range()); it != end(); ++it)
      {
        ImageSubresourceState subState;
        if(it->state().Update(oIt->state(), subState, compose))
        {
          if(!didSplit)
          {
            Split(oIt->range());
            didSplit = true;
          }
          RDCASSERT(it->range().ContainedIn(oIt->range()));
          it->SetState(subState);
          maxRefType = ComposeFrameRefsDisjoint(maxRefType, subState.refType);
        }
      }
    }
    return maxRefType;
  }
  
  size_t ImageSubresourceMap::SubresourceIndex(uint32_t aspectIndex, uint32_t level, uint32_t layer,
                                               uint32_t slice) const
  {
    if(!AreAspectsSplit())
      aspectIndex = 0;
    int splitLevelCount = 1;
    if(AreLevelsSplit())
      splitLevelCount = GetImageInfo().levelCount;
    else
      level = 0;
    int splitLayerCount = 1;
    if(AreLayersSplit())
      splitLayerCount = GetImageInfo().layerCount;
    else
      layer = 0;
    int splitSliceCount = 1;
    if(IsDepthSplit())
      splitSliceCount = GetImageInfo().extent.depth;
    else
      slice = 0;
    return ((aspectIndex * splitLevelCount + level) * splitLayerCount + layer) * splitSliceCount +
           slice;
  }
  
  void ImageSubresourceMap::ToArray(rdcarray<ImageSubresourceStateForRange> &arr)
  {
    arr.reserve(arr.size() + m_values.size());
    for(auto src = begin(); src != end(); ++src)
    {
      arr.push_back(*src);
    }
  }
  
  void ImageSubresourceMap::FromArray(const rdcarray<ImageSubresourceStateForRange> &arr)
  {
    if(arr.empty())
    {
      RDCERR("No values for ImageSubresourceMap");
      return;
    }
    Split(arr.front().range);
    if(m_values.size() != arr.size())
    {
      RDCERR("Incorrect number of values for ImageSubresourceMap");
      return;
    }
    auto src = arr.begin();
    auto dst = begin();
    while(src != arr.end())
    {
      if(src->range != dst->range())
        RDCERR("Subresource range mismatch in ImageSubresourceMap");
      else
        dst->SetState(src->state);
      ++src;
      ++dst;
    }
  }
  
  void ImageSubresourceMap::FromImgRefs(const ImgRefs &imgRefs)
  {
    bool splitLayers = imgRefs.areLayersSplit;
    bool splitDepth = false;
    if(GetImageInfo().extent.depth > 1)
    {
      RDCASSERT(GetImageInfo().layerCount == 1);
      splitDepth = splitLayers;
      splitLayers = false;
    }
    Split(imgRefs.areAspectsSplit, imgRefs.areLevelsSplit, splitLayers, splitDepth);
    RDCASSERT(!(AreLayersSplit() && IsDepthSplit()));
  
    for(auto dstIt = begin(); dstIt != end(); ++dstIt)
    {
      int aspectIndex = imgRefs.AspectIndex((VkImageAspectFlagBits)dstIt->range().aspectMask);
      int level = (int)dstIt->range().baseMipLevel;
      int layer = (int)(dstIt->range().baseArrayLayer + dstIt->range().baseDepthSlice);
      dstIt->state().refType = imgRefs.SubresourceRef(aspectIndex, level, layer);
    }
  }
  
  bool IntervalsOverlap(uint32_t base1, uint32_t count1, uint32_t base2, uint32_t count2)
  {
    if((base1 + count1) < base1)
    {
      // integer overflow
      if(count1 != VK_REMAINING_MIP_LEVELS)
        RDCWARN("Integer overflow in interval: base=%u, count=%u", base1, count1);
      count1 = UINT32_MAX - base1;
    }
    if((base2 + count2) < base2)
    {
      // integer overflow
      if(count2 != VK_REMAINING_MIP_LEVELS)
        RDCWARN("Integer overflow in interval: base=%u, count=%u", base2, count2);
      count2 = UINT32_MAX - base2;
    }
    if(count1 == 0 || count2 == 0)
      return false;    // one of the intervals is empty, so no overlap
    if(base1 > base2)
    {
      std::swap(base1, base2);
      std::swap(count1, count2);
    }
    return base2 < base1 + count1;
  }
  
  bool IntervalContainedIn(uint32_t base1, uint32_t count1, uint32_t base2, uint32_t count2)
  {
    if((base1 + count1) < base1)
    {
      // integer overflow
      if(count1 != VK_REMAINING_MIP_LEVELS)
        RDCWARN("Integer overflow in interval: base=%u, count=%u", base1, count1);
      count1 = UINT32_MAX - base1;
    }
    if((base2 + count2) < base2)
    {
      // integer overflow
      if(count2 != VK_REMAINING_MIP_LEVELS)
        RDCWARN("Integer overflow in interval: base=%u, count=%u", base2, count2);
      count2 = UINT32_MAX - base2;
    }
    return base1 >= base2 && base1 + count1 <= base2 + count2;
  }
  
  bool SanitiseLevelRange(uint32_t &baseMipLevel, uint32_t &levelCount, uint32_t imageLevelCount)
  {
    bool res = true;
    if(baseMipLevel > imageLevelCount)
    {
      RDCWARN("baseMipLevel (%u) is greater than image levelCount (%u)", baseMipLevel, imageLevelCount);
      baseMipLevel = imageLevelCount;
      res = false;
    }
    if(levelCount == VK_REMAINING_MIP_LEVELS)
    {
      levelCount = imageLevelCount - baseMipLevel;
    }
    else if(levelCount > imageLevelCount - baseMipLevel)
    {
      RDCWARN("baseMipLevel (%u) + levelCount (%u) is greater than the image levelCount (%u)",
              baseMipLevel, levelCount, imageLevelCount);
      levelCount = imageLevelCount - baseMipLevel;
      res = false;
    }
    return res;
  }
  
  bool SanitiseLayerRange(uint32_t &baseArrayLayer, uint32_t &layerCount, uint32_t imageLayerCount)
  {
    bool res = true;
    if(baseArrayLayer > imageLayerCount)
    {
      RDCWARN("baseArrayLayer (%u) is greater than image layerCount (%u)", baseArrayLayer,
              imageLayerCount);
      baseArrayLayer = imageLayerCount;
      res = false;
    }
    if(layerCount == VK_REMAINING_ARRAY_LAYERS)
    {
      layerCount = imageLayerCount - baseArrayLayer;
    }
    else if(layerCount > imageLayerCount - baseArrayLayer)
    {
      RDCWARN("baseArrayLayer (%u) + layerCount (%u) is greater than the image layerCount (%u)",
              baseArrayLayer, layerCount, imageLayerCount);
      layerCount = imageLayerCount - baseArrayLayer;
      res = false;
    }
    return res;
  }
  
  bool SanitiseSliceRange(uint32_t &baseSlice, uint32_t &sliceCount, uint32_t imageSliceCount)
  {
    bool res = true;
    if(baseSlice > imageSliceCount)
    {
      RDCWARN("baseSlice (%u) is greater than image sliceCount (%u)", baseSlice, imageSliceCount);
      baseSlice = imageSliceCount;
      res = false;
    }
    if(sliceCount == VK_REMAINING_ARRAY_LAYERS)
    {
      sliceCount = imageSliceCount - baseSlice;
    }
    else if(sliceCount > imageSliceCount - baseSlice)
    {
      RDCWARN("baseSlice (%u) + sliceCount (%u) is greater than the image sliceCount (%u)", baseSlice,
              sliceCount, imageSliceCount);
      sliceCount = imageSliceCount - baseSlice;
      res = false;
    }
    return res;
  }
  
  template <typename Map, typename Pair>
  ImageSubresourceMap::SubresourceRangeIterTemplate<Map, Pair>::SubresourceRangeIterTemplate(
      Map &map, const ImageSubresourceRange &range)
      : m_map(&map),
        m_range(range),
        m_level(range.baseMipLevel),
        m_layer(range.baseArrayLayer),
        m_slice(range.baseDepthSlice)
  {
    m_range.Sanitise(m_map->GetImageInfo());
    m_splitFlags = (uint16_t)ImageSubresourceMap::FlagBits::IsUninitialized;
    FixSubRange();
  }
  template ImageSubresourceMap::SubresourceRangeIterTemplate<ImageSubresourceMap,
                                                             ImageSubresourceMap::SubresourcePairRef>::
      SubresourceRangeIterTemplate(ImageSubresourceMap &map, const ImageSubresourceRange &range);
  template ImageSubresourceMap::SubresourceRangeIterTemplate<
      const ImageSubresourceMap, ImageSubresourceMap::ConstSubresourcePairRef>::
      SubresourceRangeIterTemplate(const ImageSubresourceMap &map, const ImageSubresourceRange &range);
  
  template <typename Map, typename Pair>
  void ImageSubresourceMap::SubresourceRangeIterTemplate<Map, Pair>::FixSubRange()
  {
    if(m_splitFlags == m_map->m_flags)
      return;
    uint16_t oldFlags = m_splitFlags;
    m_splitFlags = m_map->m_flags;
  
    if(IsDepthSplit(m_splitFlags))
    {
      m_value.m_range.baseDepthSlice = m_slice;
      m_value.m_range.sliceCount = 1u;
    }
    else
    {
      m_value.m_range.baseDepthSlice = 0u;
      m_value.m_range.sliceCount = m_map->GetImageInfo().extent.depth;
    }
  
    if(AreLayersSplit(m_splitFlags))
    {
      m_value.m_range.baseArrayLayer = m_layer;
      m_value.m_range.layerCount = 1u;
    }
    else
    {
      m_value.m_range.baseArrayLayer = 0u;
      m_value.m_range.layerCount = m_map->GetImageInfo().layerCount;
    }
  
    if(AreLevelsSplit(m_splitFlags))
    {
      m_value.m_range.baseMipLevel = m_level;
      m_value.m_range.levelCount = 1u;
    }
    else
    {
      m_value.m_range.baseMipLevel = 0u;
      m_value.m_range.levelCount = m_map->GetImageInfo().levelCount;
    }
  
    if(!AreAspectsSplit(m_splitFlags))
    {
      m_value.m_range.aspectMask = m_map->GetImageInfo().Aspects();
    }
    else if(!AreAspectsSplit(oldFlags))
    {
      // aspects are split in the map, but are not yet split in this iterator.
      // We need to find the aspectMask.
      uint32_t i = 0;
      for(auto it = ImageAspectFlagIter::begin(m_map->GetImageInfo().Aspects());
          it != ImageAspectFlagIter::end(); ++it, ++i)
      {
        if(i >= m_aspectIndex && (((*it) & m_range.aspectMask) != 0))
        {
          m_value.m_range.aspectMask = *it;
          break;
        }
      }
      m_aspectIndex = i;
    }
  }
  template void ImageSubresourceMap::SubresourceRangeIterTemplate<
      ImageSubresourceMap, ImageSubresourceMap::SubresourcePairRef>::FixSubRange();
  template void ImageSubresourceMap::SubresourceRangeIterTemplate<
      const ImageSubresourceMap, ImageSubresourceMap::ConstSubresourcePairRef>::FixSubRange();
  
  template <typename Map, typename Pair>
  Pair *ImageSubresourceMap::SubresourceRangeIterTemplate<Map, Pair>::operator->()
  {
    FixSubRange();
    m_value.m_state = &m_map->SubresourceIndexValue(m_aspectIndex, m_level, m_layer, m_slice);
    return &m_value;
  }
  template ImageSubresourceMap::SubresourcePairRef *ImageSubresourceMap::SubresourceRangeIterTemplate<
      ImageSubresourceMap, ImageSubresourceMap::SubresourcePairRef>::operator->();
  template ImageSubresourceMap::ConstSubresourcePairRef *ImageSubresourceMap::SubresourceRangeIterTemplate<
      const ImageSubresourceMap, ImageSubresourceMap::ConstSubresourcePairRef>::operator->();
  
  template <typename Map, typename Pair>
  Pair &ImageSubresourceMap::SubresourceRangeIterTemplate<Map, Pair>::operator*()
  {
    FixSubRange();
    m_value.m_state = &m_map->SubresourceIndexValue(m_aspectIndex, m_level, m_layer, m_slice);
    return m_value;
  }
  template ImageSubresourceMap::SubresourcePairRef &ImageSubresourceMap::SubresourceRangeIterTemplate<
      ImageSubresourceMap, ImageSubresourceMap::SubresourcePairRef>::operator*();
  template ImageSubresourceMap::ConstSubresourcePairRef &ImageSubresourceMap::SubresourceRangeIterTemplate<
      const ImageSubresourceMap, ImageSubresourceMap::ConstSubresourcePairRef>::operator*();
  
  template <typename Barrier>
  void BarrierSequence<Barrier>::AddWrapped(uint32_t batchIndex, uint32_t queueFamilyIndex,
                                            const Barrier &barrier)
  {
    RDCASSERT(batchIndex < MAX_BATCH_COUNT);
    RDCASSERT(queueFamilyIndex < MAX_QUEUE_FAMILY_COUNT);
    batches[batchIndex][queueFamilyIndex].push_back(barrier);
    ++barrierCount;
  }
  template void BarrierSequence<VkImageMemoryBarrier>::AddWrapped(uint32_t batchIndex,
                                                                  uint32_t queueFamilyIndex,
                                                                  const VkImageMemoryBarrier &barrier);
  
  template <typename Barrier>
  void BarrierSequence<Barrier>::Merge(const BarrierSequence<Barrier> &other)
  {
    for(uint32_t batchIndex = 0; batchIndex < MAX_BATCH_COUNT; ++batchIndex)
    {
      rdcarray<Barrier> *batch = batches[batchIndex];
      const rdcarray<Barrier> *otherBatch = other.batches[batchIndex];
      for(uint32_t queueFamilyIndex = 0; queueFamilyIndex < MAX_QUEUE_FAMILY_COUNT; ++queueFamilyIndex)
      {
        rdcarray<Barrier> &barriers = batch[queueFamilyIndex];
        const rdcarray<Barrier> &otherBarriers = otherBatch[queueFamilyIndex];
        barriers.insert(barriers.size(), otherBarriers.begin(), otherBarriers.size());
        barrierCount += otherBarriers.size();
      }
    }
  }
  template void BarrierSequence<VkImageMemoryBarrier>::Merge(
      const BarrierSequence<VkImageMemoryBarrier> &other);
  
  template <typename Barrier>
  bool BarrierSequence<Barrier>::IsBatchEmpty(uint32_t batchIndex) const
  {
    if(batchIndex >= MAX_BATCH_COUNT)
      return true;
    for(uint32_t queueFamilyIndex = 0; queueFamilyIndex < MAX_QUEUE_FAMILY_COUNT; ++queueFamilyIndex)
    {
      if(!batches[batchIndex][queueFamilyIndex].empty())
        return false;
    }
    return true;
  }
  template bool BarrierSequence<VkImageMemoryBarrier>::IsBatchEmpty(uint32_t batchIndex) const;
  
  template <>
  void BarrierSequence<VkImageMemoryBarrier>::UnwrapBarriers(rdcarray<VkImageMemoryBarrier> &barriers)
  {
    for(auto it = barriers.begin(); it != barriers.end(); ++it)
    {
      it->image = ::Unwrap(it->image);
    }
  }
  
  template <typename Barrier>
  void BarrierSequence<Barrier>::ExtractUnwrappedBatch(uint32_t batchIndex, uint32_t queueFamilyIndex,
                                                       rdcarray<Barrier> &result)
  {
    if(batchIndex >= MAX_BATCH_COUNT || queueFamilyIndex >= MAX_QUEUE_FAMILY_COUNT)
      return;
    rdcarray<Barrier> &batch = batches[batchIndex][queueFamilyIndex];
    batch.swap(result);
    batch.clear();
    barrierCount -= result.size();
    UnwrapBarriers(result);
  }
  template void BarrierSequence<VkImageMemoryBarrier>::ExtractUnwrappedBatch(
      uint32_t batchIndex, uint32_t queueFamilyIndex, rdcarray<VkImageMemoryBarrier> &result);
  
  template <typename Barrier>
  void BarrierSequence<Barrier>::ExtractFirstUnwrappedBatchForQueue(uint32_t queueFamilyIndex,
                                                                    rdcarray<Barrier> &result)
  {
    for(uint32_t batchIndex = 0; batchIndex < MAX_BATCH_COUNT; ++batchIndex)
    {
      if(!IsBatchEmpty(batchIndex))
      {
        batches[batchIndex][queueFamilyIndex].swap(result);
        batches[batchIndex][queueFamilyIndex].clear();
        barrierCount -= result.size();
        UnwrapBarriers(result);
        return;
      }
    }
  }
  template void BarrierSequence<VkImageMemoryBarrier>::ExtractFirstUnwrappedBatchForQueue(
      uint32_t queueFamilyIndex, rdcarray<VkImageMemoryBarrier> &result);
  
  template <typename Barrier>
  void BarrierSequence<Barrier>::ExtractLastUnwrappedBatchForQueue(uint32_t queueFamilyIndex,
                                                                   rdcarray<Barrier> &result)
  {
    for(uint32_t batchIndex = MAX_BATCH_COUNT; batchIndex > 0;)
    {
      --batchIndex;
      if(!IsBatchEmpty(batchIndex))
      {
        batches[batchIndex][queueFamilyIndex].swap(result);
        batches[batchIndex][queueFamilyIndex].clear();
        barrierCount -= result.size();
        UnwrapBarriers(result);
        return;
      }
    }
  }
  template void BarrierSequence<VkImageMemoryBarrier>::ExtractLastUnwrappedBatchForQueue(
      uint32_t queueFamilyIndex, rdcarray<VkImageMemoryBarrier> &result);
  
  ImageState ImageState::InitialState() const
  {
    ImageState result(wrappedHandle, GetImageInfo(), eFrameRef_Unknown);
    InitialState(result);
    return result;
  }
  
  void ImageState::InitialState(ImageState &result) const
  {
    result.subresourceStates = subresourceStates;
    for(auto it = result.subresourceStates.begin(); it != result.subresourceStates.end(); ++it)
    {
      ImageSubresourceState &sub = it->state();
      sub.newLayout = sub.oldLayout = GetImageInfo().initialLayout;
      sub.newQueueFamilyIndex = sub.oldQueueFamilyIndex;
      sub.refType = eFrameRef_Unknown;
    }
  }
  
  ImageState ImageState::CommandBufferInitialState() const
  {
    ImageSubresourceState sub;
    sub.oldLayout = sub.newLayout = UNKNOWN_PREV_IMG_LAYOUT;
    return UniformState(sub);
  }
  
  ImageState ImageState::UniformState(const ImageSubresourceState &sub) const
  {
    ImageState result(wrappedHandle, GetImageInfo(), eFrameRef_None);
    result.subresourceStates.begin()->SetState(sub);
    return result;
  }
  
  ImageState ImageState::ContentInitializationState(InitPolicy policy, bool initialized,
                                                    uint32_t queueFamilyIndex, VkImageLayout copyLayout,
                                                    VkImageLayout clearLayout) const
  {
    ImageState result = *this;
    for(auto it = result.subresourceStates.begin(); it != result.subresourceStates.end(); ++it)
    {
      ImageSubresourceState &sub = it->state();
      InitReqType initReq = InitReq(sub.refType, policy, initialized);
      if(initReq == eInitReq_None)
        continue;
      sub.newQueueFamilyIndex = queueFamilyIndex;
      if(initReq == eInitReq_Copy)
        sub.newLayout = copyLayout;
      else if(initReq == eInitReq_Clear)
        sub.newLayout = clearLayout;
    }
    return result;
  }
  
  void ImageState::RemoveQueueFamilyTransfer(VkImageMemoryBarrier *it)
  {
    if(it < newQueueFamilyTransfers.begin() || it >= newQueueFamilyTransfers.end())
      RDCERR("Attempting to remove queue family transfer at invalid address");
    std::swap(*it, newQueueFamilyTransfers.back());
    newQueueFamilyTransfers.erase(newQueueFamilyTransfers.size() - 1);
  }
  
  void ImageState::Update(ImageSubresourceRange range, const ImageSubresourceState &dst,
                          FrameRefCompFunc compose)
  {
    range.Sanitise(GetImageInfo());
  
    bool didSplit = false;
    for(auto it = subresourceStates.RangeBegin(range); it != subresourceStates.end(); ++it)
    {
      ImageSubresourceState subState;
      if(it->state().Update(dst, subState, compose))
      {
        if(!didSplit)
        {
          subresourceStates.Split(range);
          didSplit = true;
        }
        RDCASSERT(it->range().ContainedIn(range));
        it->SetState(subState);
        maxRefType = ComposeFrameRefsDisjoint(maxRefType, subState.refType);
      }
    }
  }
  
  void ImageState::Merge(const ImageState &other, ImageTransitionInfo info)
  {
    if(wrappedHandle == VK_NULL_HANDLE)
      wrappedHandle = other.wrappedHandle;
    for(auto it = other.oldQueueFamilyTransfers.begin(); it != other.oldQueueFamilyTransfers.end(); ++it)
    {
      RecordQueueFamilyAcquire(*it);
    }
    maxRefType = subresourceStates.Merge(other.subresourceStates, info.GetFrameRefCompFunc());
    for(auto it = other.newQueueFamilyTransfers.begin(); it != other.newQueueFamilyTransfers.end(); ++it)
    {
      RecordQueueFamilyRelease(*it);
    }
  }
  
  void ImageState::MergeCaptureBeginState(const ImageState &initialState)
  {
    oldQueueFamilyTransfers = initialState.oldQueueFamilyTransfers;
    subresourceStates.Merge(initialState.subresourceStates, ComposeFrameRefsFirstKnown);
    maxRefType = initialState.maxRefType;
  }
  
  void ImageState::Merge(std::map<ResourceId, ImageState> &states,
                         const std::map<ResourceId, ImageState> &dstStates, ImageTransitionInfo info)
  {
    auto it = states.begin();
    auto dstIt = dstStates.begin();
    while(dstIt != dstStates.end())
    {
      if(it == states.end() || dstIt->first < it->first)
      {
        it = states.insert(it, {dstIt->first, dstIt->second.InitialState()});
      }
      else if(it->first < dstIt->first)
      {
        ++it;
        continue;
      }
  
      it->second.Merge(dstIt->second, info);
      ++it;
      ++dstIt;
    }
  }
  
  void ImageState::DiscardContents(const ImageSubresourceRange &range)
  {
    Update(range, ImageSubresourceState(VK_QUEUE_FAMILY_IGNORED, VK_IMAGE_LAYOUT_UNDEFINED),
           KeepOldFrameRef);
  }
  
  void ImageState::RecordQueueFamilyRelease(const VkImageMemoryBarrier &barrier)
  {
    for(auto it = newQueueFamilyTransfers.begin(); it != newQueueFamilyTransfers.end(); ++it)
    {
      if(ImageSubresourceRange(barrier.subresourceRange).Overlaps(it->subresourceRange))
      {
        RDCWARN("Queue family release barriers overlap");
        RemoveQueueFamilyTransfer(it);
        --it;
      }
    }
    newQueueFamilyTransfers.push_back(barrier);
  }
  
  void ImageState::RecordQueueFamilyAcquire(const VkImageMemoryBarrier &barrier)
  {
    bool foundRelease = false;
    ImageSubresourceRange acquireRange(barrier.subresourceRange);
    for(auto it = newQueueFamilyTransfers.begin(); it != newQueueFamilyTransfers.end(); ++it)
    {
      ImageSubresourceRange releaseRange(it->subresourceRange);
      if(acquireRange.Overlaps(releaseRange))
      {
        if(acquireRange != releaseRange)
          RDCWARN(
              "Overlapping queue family release and acquire barriers have different "
              "subresourceRange");
        if(barrier.srcQueueFamilyIndex != it->srcQueueFamilyIndex ||
           barrier.dstQueueFamilyIndex != it->dstQueueFamilyIndex)
          RDCWARN("Queue family mismatch between release and acquire barriers");
        if(barrier.oldLayout != it->oldLayout || barrier.newLayout != it->newLayout)
          RDCWARN("Image layouts mismatch between release and acquire barriers");
        if(foundRelease)
          RDCWARN("Found multiple release barriers for acquire barrier");
        RemoveQueueFamilyTransfer(it);
        --it;
        foundRelease = true;
      }
    }
    if(!foundRelease)
    {
      oldQueueFamilyTransfers.push_back(barrier);
    }
  }
  
  void ImageState::RecordBarrier(VkImageMemoryBarrier barrier, uint32_t queueFamilyIndex,
                                 ImageTransitionInfo info)
  {
    if(barrier.srcQueueFamilyIndex == VK_QUEUE_FAMILY_EXTERNAL ||
       barrier.srcQueueFamilyIndex == VK_QUEUE_FAMILY_FOREIGN_EXT ||
       barrier.dstQueueFamilyIndex == VK_QUEUE_FAMILY_EXTERNAL ||
       barrier.dstQueueFamilyIndex == VK_QUEUE_FAMILY_FOREIGN_EXT)
    {
      RDCERR("External/foreign queue families are not supported");
      return;
    }
    if(GetImageInfo().sharingMode == VK_SHARING_MODE_CONCURRENT)
    {
      if(!(barrier.srcQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED &&
           barrier.dstQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED))
      {
        RDCWARN("Barrier contains invalid queue families for VK_SHARING_MODE_CONCURRENT");
      }
      barrier.srcQueueFamilyIndex = barrier.dstQueueFamilyIndex = queueFamilyIndex;
    }
    else if(GetImageInfo().sharingMode == VK_SHARING_MODE_EXCLUSIVE)
    {
      if(barrier.srcQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED ||
         barrier.dstQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
      {
        if(barrier.srcQueueFamilyIndex != VK_QUEUE_FAMILY_IGNORED ||
           barrier.dstQueueFamilyIndex != VK_QUEUE_FAMILY_IGNORED)
        {
          RDCERR("Barrier contains invalid queue families for VK_SHARING_MODE_EXCLUSIVE: (%s, %s)",
                 ToStr(barrier.srcQueueFamilyIndex).c_str(),
                 ToStr(barrier.dstQueueFamilyIndex).c_str());
          return;
        }
        barrier.srcQueueFamilyIndex = queueFamilyIndex;
        barrier.dstQueueFamilyIndex = queueFamilyIndex;
      }
      else if(barrier.srcQueueFamilyIndex == queueFamilyIndex)
      {
        if(barrier.dstQueueFamilyIndex != queueFamilyIndex)
        {
          RecordQueueFamilyRelease(barrier);
          // Skip the updates to the subresource states.
          // These will be updated by the acquire.
          // This allows us to restore a released-but-not-acquired state by first transitioning to the
          // subresource states (which will match the srcQueueFamilyIndex/oldLayout), and then
          // applying the release barrier.
          return;
        }
      }
      else if(barrier.dstQueueFamilyIndex == queueFamilyIndex)
      {
        RecordQueueFamilyAcquire(barrier);
      }
      else
      {
        RDCERR("Ownership transfer from queue family %u to %u submitted to queue family %u",
               barrier.srcQueueFamilyIndex, barrier.dstAccessMask, queueFamilyIndex);
      }
    }
  
    Update(barrier.subresourceRange, ImageSubresourceState(barrier), info.GetFrameRefCompFunc());
  }
  
  bool ImageState::CloseTransfers(uint32_t batchIndex, VkAccessFlags dstAccessMask,
                                  ImageBarrierSequence &barriers, ImageTransitionInfo info)
  {
    if(newQueueFamilyTransfers.empty())
      return false;
    FrameRefCompFunc compose = info.GetFrameRefCompFunc();
    for(auto it = newQueueFamilyTransfers.begin(); it != newQueueFamilyTransfers.end(); ++it)
    {
      Update(it->subresourceRange, ImageSubresourceState(it->dstQueueFamilyIndex, it->newLayout),
             compose);
  
      it->dstAccessMask = dstAccessMask;
      it->image = wrappedHandle;
      barriers.AddWrapped(batchIndex, it->dstQueueFamilyIndex, *it);
    }
    newQueueFamilyTransfers.clear();
    return true;
  }
  
  bool ImageState::RestoreTransfers(uint32_t batchIndex,
                                    const rdcarray<VkImageMemoryBarrier> &transfers,
                                    VkAccessFlags srcAccessMask, ImageBarrierSequence &barriers,
                                    ImageTransitionInfo info)
  {
    // TODO: figure out why `transfers` has duplicate entries
    if(transfers.empty())
      return false;
    for(auto it = transfers.begin(); it != transfers.end(); ++it)
    {
      VkImageMemoryBarrier barrier = *it;
      barrier.srcAccessMask = srcAccessMask;
      barrier.image = wrappedHandle;
      barriers.AddWrapped(batchIndex, barrier.srcQueueFamilyIndex, barrier);
      RecordQueueFamilyRelease(barrier);
    }
    return true;
  }
  
  void ImageState::ResetToOldState(ImageBarrierSequence &barriers, ImageTransitionInfo info)
  {
    VkAccessFlags srcAccessMask = VK_ACCESS_ALL_WRITE_BITS;
    VkAccessFlags dstAccessMask = VK_ACCESS_ALL_READ_BITS;
    const uint32_t CLOSE_TRANSFERS_BATCH_INDEX = 0;
    const uint32_t MAIN_BATCH_INDEX = 1;
    const uint32_t ACQUIRE_BATCH_INDEX = 2;
    const uint32_t RESTORE_TRANSFERS_BATCH_INDEX = 3;
    CloseTransfers(CLOSE_TRANSFERS_BATCH_INDEX, dstAccessMask, barriers, info);
  
    for(auto subIt = subresourceStates.begin(); subIt != subresourceStates.end(); ++subIt)
    {
      VkImageLayout oldLayout = subIt->state().newLayout;
      if(oldLayout == UNKNOWN_PREV_IMG_LAYOUT)
        oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
      VkImageLayout newLayout = subIt->state().oldLayout;
      subIt->state().newLayout = subIt->state().oldLayout;
      if(newLayout == UNKNOWN_PREV_IMG_LAYOUT || newLayout == VK_IMAGE_LAYOUT_UNDEFINED)
      {
        // contents discarded, no barrier necessary
        continue;
      }
      SanitiseReplayImageLayout(oldLayout);
      SanitiseReplayImageLayout(newLayout);
      if(oldLayout != VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_PREINITIALIZED)
      {
        // Transitioning back to PREINITIALIZED; this is impossible, so transition to GENERAL instead.
        newLayout = VK_IMAGE_LAYOUT_GENERAL;
      }
  
      uint32_t srcQueueFamilyIndex = subIt->state().newQueueFamilyIndex;
      uint32_t dstQueueFamilyIndex = subIt->state().oldQueueFamilyIndex;
  
      if(srcQueueFamilyIndex == VK_QUEUE_FAMILY_EXTERNAL ||
         srcQueueFamilyIndex == VK_QUEUE_FAMILY_FOREIGN_EXT)
      {
        srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
      }
      if(dstQueueFamilyIndex == VK_QUEUE_FAMILY_EXTERNAL ||
         dstQueueFamilyIndex == VK_QUEUE_FAMILY_FOREIGN_EXT)
      {
        dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
      }
  
      uint32_t submitQueueFamilyIndex = srcQueueFamilyIndex;
  
      if(GetImageInfo().sharingMode == VK_SHARING_MODE_EXCLUSIVE)
      {
        if(srcQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
        {
          submitQueueFamilyIndex = dstQueueFamilyIndex;
          dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        }
        else if(dstQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
        {
          srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        }
      }
      else
      {
        if(submitQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
          submitQueueFamilyIndex = dstQueueFamilyIndex;
        srcQueueFamilyIndex = dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
      }
  
      if(srcQueueFamilyIndex == dstQueueFamilyIndex && oldLayout == newLayout)
      {
        subIt->state().newQueueFamilyIndex = subIt->state().oldQueueFamilyIndex;
        continue;
      }
  
      if(submitQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
      {
        RDCWARN(
            "ResetToOldState: barrier submitted to VK_QUEUE_FAMILY_IGNORED; defaulting to queue "
            "family %u",
            info.defaultQueueFamilyIndex);
        submitQueueFamilyIndex = info.defaultQueueFamilyIndex;
      }
      subIt->state().newQueueFamilyIndex = subIt->state().oldQueueFamilyIndex;
  
      ImageSubresourceRange subRange = subIt->range();
  
      if(subRange.baseDepthSlice != 0)
      {
        // We can't issue barriers per depth slice, so skip the barriers for non-zero depth slices.
        // The zero depth slice barrier will implicitly cover the non-zerp depth slices.
        continue;
      }
  
      if((GetImageInfo().Aspects() & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) ==
             (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) &&
         !info.separateDepthStencil)
      {
        // This is a subresource of a depth and stencil image, and
        // VK_KHR_separate_depth_stencil_layouts is not enabled, so the barrier needs to include both
        // depth and stencil aspects. We skip the stencil-only aspect and expand the barrier for the
        // depth-only aspect to include both depth and stencil aspects.
        if(subRange.aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT)
          continue;
        if(subRange.aspectMask == VK_IMAGE_ASPECT_DEPTH_BIT)
          subRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
      }
  
      VkImageMemoryBarrier barrier = {
          /* sType = */ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
          /* pNext = */ NULL,
          /* srcAccessMask = */ srcAccessMask,
          /* dstAccessMask = */ dstAccessMask,
          /* oldLayout = */ oldLayout,
          /* newLayout = */ newLayout,
          /* srcQueueFamilyIndex = */ srcQueueFamilyIndex,
          /* dstQueueFamilyIndex = */ dstQueueFamilyIndex,
          /* image = */ wrappedHandle,
          /* subresourceRange = */ subRange,
      };
      barriers.AddWrapped(MAIN_BATCH_INDEX, submitQueueFamilyIndex, barrier);
  
      // acquire the subresource in the dstQueueFamily, if necessary
      if(barrier.srcQueueFamilyIndex != barrier.dstQueueFamilyIndex)
      {
        barriers.AddWrapped(ACQUIRE_BATCH_INDEX, barrier.dstQueueFamilyIndex, barrier);
      }
    }
    RestoreTransfers(RESTORE_TRANSFERS_BATCH_INDEX, oldQueueFamilyTransfers, srcAccessMask, barriers,
                     info);
  }
  
  void ImageState::Transition(const ImageState &dstState, VkAccessFlags srcAccessMask,
                              VkAccessFlags dstAccessMask, ImageBarrierSequence &barriers,
                              ImageTransitionInfo info)
  {
    const uint32_t CLOSE_TRANSFERS_BATCH_INDEX = 0;
    const uint32_t MAIN_BATCH_INDEX = 1;
    const uint32_t ACQUIRE_BATCH_INDEX = 2;
    const uint32_t RESTORE_TRANSFERS_BATCH_INDEX = 3;
    CloseTransfers(CLOSE_TRANSFERS_BATCH_INDEX, dstAccessMask, barriers, info);
  
    for(auto dstIt = dstState.subresourceStates.begin(); dstIt != dstState.subresourceStates.end();
        ++dstIt)
    {
      const ImageSubresourceRange &dstRng = dstIt->range();
      const ImageSubresourceState &dstSub = dstIt->state();
      for(auto it = subresourceStates.RangeBegin(dstRng); it != subresourceStates.end(); ++it)
      {
        ImageSubresourceState srcSub;
        if(!it->state().Update(dstSub, srcSub, info.GetFrameRefCompFunc()))
          // subresource state did not change, so no need for a barrier
          continue;
  
        subresourceStates.Split(dstRng);
        std::swap(it->state(), srcSub);
  
        ImageSubresourceRange srcRng = it->range();
  
        VkImageLayout oldLayout = srcSub.newLayout;
        if(oldLayout == UNKNOWN_PREV_IMG_LAYOUT)
          oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        VkImageLayout newLayout = dstSub.newLayout;
        if(newLayout == UNKNOWN_PREV_IMG_LAYOUT || newLayout == VK_IMAGE_LAYOUT_UNDEFINED)
          // ignore transitions to undefined
          continue;
        uint32_t srcQueueFamilyIndex = srcSub.newQueueFamilyIndex;
        uint32_t dstQueueFamilyIndex = dstSub.newQueueFamilyIndex;
  
        if(oldLayout == VK_IMAGE_LAYOUT_UNDEFINED)
          // transitions from undefined discard the contents anyway, so no queue family ownership
          // transfer is necessary
          srcQueueFamilyIndex = dstQueueFamilyIndex;
  
        if(newLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && oldLayout != VK_IMAGE_LAYOUT_PREINITIALIZED)
        {
          // Transitioning to PREINITIALIZED, which is invalid. This happens when we are resetting to
          // an earlier image state.
          // Instead, we transition to GENERAL, and make the image owned by oldQueueFamilyIndex.
          newLayout = VK_IMAGE_LAYOUT_GENERAL;
          dstQueueFamilyIndex = srcSub.oldQueueFamilyIndex;
          RDCASSERT(dstQueueFamilyIndex != VK_QUEUE_FAMILY_IGNORED);
        }
  
        if(IsReplayMode(info.capState))
        {
          // Get rid of PRESENT layouts
          SanitiseReplayImageLayout(oldLayout);
          SanitiseReplayImageLayout(newLayout);
        }
  
        uint32_t submitQueueFamilyIndex = (srcQueueFamilyIndex != VK_QUEUE_FAMILY_IGNORED)
                                              ? srcQueueFamilyIndex
                                              : dstQueueFamilyIndex;
        if(submitQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED ||
           submitQueueFamilyIndex == VK_QUEUE_FAMILY_EXTERNAL ||
           submitQueueFamilyIndex == VK_QUEUE_FAMILY_FOREIGN_EXT)
        {
          RDCERR("Ignoring state transition submitted to invalid queue family %u",
                 submitQueueFamilyIndex);
          continue;
        }
        if(GetImageInfo().sharingMode == VK_SHARING_MODE_CONCURRENT)
        {
          srcQueueFamilyIndex = dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        }
        else
        {
          if(srcQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
          {
            RDCWARN("ImageState::Transition: src queue family == VK_QUEUE_FAMILY_IGNORED.");
            srcQueueFamilyIndex = dstQueueFamilyIndex;
          }
          if(dstQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
          {
            RDCWARN("ImageState::Transition: dst queue family == VK_QUEUE_FAMILY_IGNORED.");
            dstQueueFamilyIndex = srcQueueFamilyIndex;
          }
        }
  
        if(srcQueueFamilyIndex == dstQueueFamilyIndex && oldLayout == newLayout)
          // Skip the barriers, because it would do nothing
          continue;
  
        if(srcRng.baseDepthSlice != 0 || dstRng.baseDepthSlice != 0)
        {
          // We can't issue barriers per depth slice, so skip the barriers for non-zero depth slices.
          // The zero depth slice barrier will implicitly cover the non-zerp depth slices.
          continue;
        }
  
        VkImageAspectFlags aspectMask = srcRng.aspectMask & dstRng.aspectMask;
        if((GetImageInfo().Aspects() & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) ==
               (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) &&
           !info.separateDepthStencil)
        {
          // This is a subresource of a depth and stencil image, and
          // VK_KHR_separate_depth_stencil_layouts is not enabled, so the barrier needs to include
          // both depth and stencil aspects. We skip the stencil-only aspect and expand the barrier
          // for the depth-only aspect to include both depth and stencil aspects.
          if(aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT)
            continue;
          if(aspectMask == VK_IMAGE_ASPECT_DEPTH_BIT)
            aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
        }
        uint32_t baseMipLevel = RDCMAX(dstRng.baseMipLevel, srcRng.baseMipLevel);
        uint32_t endMipLevel =
            RDCMIN(dstRng.baseMipLevel + dstRng.levelCount, srcRng.baseMipLevel + srcRng.levelCount);
        uint32_t baseArrayLayer = RDCMAX(dstRng.baseArrayLayer, srcRng.baseArrayLayer);
        uint32_t endArrayLayer = RDCMIN(dstRng.baseArrayLayer + dstRng.layerCount,
                                        srcRng.baseArrayLayer + srcRng.layerCount);
        VkImageMemoryBarrier barrier = {
            /* sType = */ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
            /* pNext = */ NULL,
            /* srcAccessMask = */ srcAccessMask,
            /* dstAccessMask = */ dstAccessMask,
            /* oldLayout = */ oldLayout,
            /* newLayout = */ newLayout,
            /* srcQueueFamilyIndex = */ srcQueueFamilyIndex,
            /* dstQueueFamilyIndex = */ dstQueueFamilyIndex,
            /* image = */ wrappedHandle,
            /* subresourceRange = */
            {
                /* aspectMask = */ aspectMask,
                /* baseMipLevel = */ baseMipLevel,
                /* levelCount = */ endMipLevel - baseMipLevel,
                /* baseArrayLayer = */ baseArrayLayer,
                /* layerCount = */ endArrayLayer - baseArrayLayer,
            },
        };
        barriers.AddWrapped(MAIN_BATCH_INDEX, submitQueueFamilyIndex, barrier);
  
        // acquire the subresource in the dstQueueFamily, if necessary
        if(barrier.srcQueueFamilyIndex != barrier.dstQueueFamilyIndex)
        {
          barriers.AddWrapped(ACQUIRE_BATCH_INDEX, barrier.dstQueueFamilyIndex, barrier);
        }
      }
    }
    RestoreTransfers(RESTORE_TRANSFERS_BATCH_INDEX, dstState.newQueueFamilyTransfers, srcAccessMask,
                     barriers, info);
  }
  
  void ImageState::Transition(uint32_t queueFamilyIndex, VkImageLayout layout,
                              VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask,
                              ImageBarrierSequence &barriers, ImageTransitionInfo info)
  {
    Transition(UniformState(ImageSubresourceState(queueFamilyIndex, layout)), srcAccessMask,
               dstAccessMask, barriers, info);
  }
  
  void ImageState::TempTransition(const ImageState &dstState, VkAccessFlags preSrcAccessMask,
                                  VkAccessFlags preDstAccessMask, VkAccessFlags postSrcAccessmask,
                                  VkAccessFlags postDstAccessMask, ImageBarrierSequence &setupBarriers,
                                  ImageBarrierSequence &cleanupBarriers, ImageTransitionInfo info) const
  {
    ImageState temp(*this);
    temp.Transition(dstState, preSrcAccessMask, preDstAccessMask, setupBarriers, info);
    temp.Transition(*this, postSrcAccessmask, postDstAccessMask, cleanupBarriers, info);
  }
  
  void ImageState::TempTransition(uint32_t queueFamilyIndex, VkImageLayout layout,
                                  VkAccessFlags accessMask, ImageBarrierSequence &setupBarriers,
                                  ImageBarrierSequence &cleanupBarriers, ImageTransitionInfo info) const
  {
    TempTransition(UniformState(ImageSubresourceState(queueFamilyIndex, layout)),
                   VK_ACCESS_ALL_WRITE_BITS, accessMask, accessMask, VK_ACCESS_ALL_READ_BITS,
                   setupBarriers, cleanupBarriers, info);
  }
  
  void ImageState::InlineTransition(VkCommandBuffer cmd, uint32_t queueFamilyIndex,
                                    const ImageState &dstState, VkAccessFlags srcAccessMask,
                                    VkAccessFlags dstAccessMask, ImageTransitionInfo info)
  {
    ImageBarrierSequence barriers;
    Transition(dstState, srcAccessMask, dstAccessMask, barriers, info);
    if(barriers.empty())
      return;
    rdcarray<VkImageMemoryBarrier> barriersArray;
    barriers.ExtractFirstUnwrappedBatchForQueue(queueFamilyIndex, barriersArray);
    if(!barriersArray.empty())
      DoPipelineBarrier(cmd, (uint32_t)barriersArray.size(), barriersArray.data());
    if(!barriers.empty())
    {
      RDCERR("Could not inline all image state transition barriers");
    }
  }
  
  void ImageState::InlineTransition(VkCommandBuffer cmd, uint32_t queueFamilyIndex,
                                    VkImageLayout layout, VkAccessFlags srcAccessMask,
                                    VkAccessFlags dstAccessMask, ImageTransitionInfo info)
  {
    InlineTransition(cmd, queueFamilyIndex,
                     UniformState(ImageSubresourceState(queueFamilyIndex, layout)), srcAccessMask,
                     dstAccessMask, info);
  }
  
  InitReqType ImageState::MaxInitReq(const ImageSubresourceRange &range, InitPolicy policy,
                                     bool initialized) const
  {
    FrameRefType refType = eFrameRef_None;
    for(auto it = subresourceStates.RangeBegin(range); it != subresourceStates.end(); ++it)
    {
      refType = ComposeFrameRefsDisjoint(refType, it->state().refType);
    }
    return InitReq(refType, policy, initialized);
  }
  
  VkImageLayout ImageState::GetImageLayout(VkImageAspectFlagBits aspect, uint32_t mipLevel,
                                           uint32_t arrayLayer) const
  {
    return subresourceStates.SubresourceAspectValue(aspect, mipLevel, arrayLayer, 0).newLayout;
  }
  
  void ImageState::BeginCapture()
  {
    maxRefType = eFrameRef_None;
  
    // Forget any pending queue family release operations.
    // If the matching queue family acquire operation happens during the frame,
    // an implicit release operation will be put into `oldQueueFamilyTransfers`.
    newQueueFamilyTransfers.clear();
  
    // Also clear implicit queue family acquire operations because these correspond to release
    // operations already submitted (and therefore not part of the capture).
    oldQueueFamilyTransfers.clear();
  
    for(auto it = subresourceStates.begin(); it != subresourceStates.end(); ++it)
    {
      ImageSubresourceState state = it->state();
      state.oldLayout = state.newLayout;
      state.oldQueueFamilyIndex = state.newQueueFamilyIndex;
      state.refType = eFrameRef_None;
      it->SetState(state);
    }
  }
  