/******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2019-2022 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 "common/globalconfig.h"

#if ENABLED(ENABLE_UNIT_TESTS)

#include "catch/catch.hpp"

#include "vk_resources.h"

#include <stdint.h>

void CheckSubresourceRanges(const ImageState &state, bool expectAspectsSplit,
                            bool expectLevelsSplit, bool expectLayersSplit, bool expectDepthSplit)
{
  rdcarray<VkImageAspectFlags> splitAspects;
  if(expectAspectsSplit)
  {
    for(auto it = ImageAspectFlagIter::begin(state.GetImageInfo().Aspects());
        it != ImageAspectFlagIter::end(); ++it)
    {
      splitAspects.push_back(*it);
    }
  }
  else
  {
    splitAspects.push_back(state.GetImageInfo().Aspects());
  }
  uint32_t splitLevelCount = expectLevelsSplit ? state.GetImageInfo().levelCount : 1;
  uint32_t splitLayerCount = expectLayersSplit ? state.GetImageInfo().layerCount : 1;
  uint32_t splitSliceCount = expectDepthSplit ? state.GetImageInfo().extent.depth : 1;
  size_t splitSize = splitAspects.size() * (size_t)splitLevelCount * (size_t)splitLayerCount *
                     (size_t)splitSliceCount;
  CHECK(state.subresourceStates.size() == splitSize);

  auto substateIt = state.subresourceStates.begin();
  auto aspectIt = splitAspects.begin();
  uint32_t level = 0;
  uint32_t layer = 0;
  uint32_t slice = 0;
  uint32_t index = 0;
  for(; aspectIt != splitAspects.end() && substateIt != state.subresourceStates.end();
      ++substateIt, ++index)
  {
    const ImageSubresourceRange &range = substateIt->range();
    CHECK(range.aspectMask == *aspectIt);

    if(expectLevelsSplit)
    {
      CHECK(range.baseMipLevel == level);
      CHECK(range.levelCount == 1);
    }
    else
    {
      CHECK(range.baseMipLevel == 0);
      CHECK(range.levelCount == (uint32_t)state.GetImageInfo().levelCount);
    }

    if(expectLayersSplit)
    {
      CHECK(range.baseArrayLayer == layer);
      CHECK(range.layerCount == 1);
    }
    else
    {
      CHECK(range.baseArrayLayer == 0);
      CHECK(range.layerCount == (uint32_t)state.GetImageInfo().layerCount);
    }

    if(expectDepthSplit)
    {
      CHECK(range.baseDepthSlice == slice);
      CHECK(range.sliceCount == 1);
    }
    else
    {
      CHECK(range.baseDepthSlice == 0);
      CHECK(range.sliceCount == (uint32_t)state.GetImageInfo().extent.depth);
    }

    ++slice;
    if(slice < splitSliceCount)
      continue;
    slice = 0;

    ++layer;
    if(layer < splitLayerCount)
      continue;
    layer = 0;

    ++level;
    if(level < splitLevelCount)
      continue;
    level = 0;

    ++aspectIt;
  }
  CHECK(index == splitSize);
}

void CheckSubresourceState(const ImageSubresourceState &substate,
                           const ImageSubresourceState &expected)
{
  CHECK(substate.oldQueueFamilyIndex == expected.oldQueueFamilyIndex);
  CHECK(substate.newQueueFamilyIndex == expected.newQueueFamilyIndex);
  CHECK(substate.oldLayout == expected.oldLayout);
  CHECK(substate.newLayout == expected.newLayout);
  CHECK(substate.refType == expected.refType);
}

TEST_CASE("Test ImageState type", "[imagestate]")
{
  ImageTransitionInfo transitionInfo(CaptureState::ActiveCapturing, 0, true);
  VkImage image = (VkImage)123;
  VkFormat format = VK_FORMAT_D16_UNORM_S8_UINT;
  VkExtent3D extent = {100, 100, 13};
  uint16_t levelCount = 11;
  uint32_t layerCount = 17;
  uint16_t sampleCount = 1;
  ImageInfo imageInfo(format, extent, levelCount, layerCount, sampleCount,
                      VK_IMAGE_LAYOUT_UNDEFINED, VK_SHARING_MODE_EXCLUSIVE);

  ImageSubresourceState initSubstate(VK_QUEUE_FAMILY_IGNORED, UNKNOWN_PREV_IMG_LAYOUT,
                                     eFrameRef_None);

  ImageSubresourceState readSubstate(initSubstate);
  readSubstate.oldQueueFamilyIndex = readSubstate.newQueueFamilyIndex = 0;
  readSubstate.refType = eFrameRef_Read;

  SECTION("Initial state")
  {
    ImageState state(image, imageInfo, eFrameRef_None);
    CheckSubresourceRanges(state, false, false, false, false);
    CheckSubresourceState(state.subresourceStates.begin()->state(), initSubstate);
  };

  SECTION("Split aspects")
  {
    ImageState state(image, imageInfo, eFrameRef_None);
    ImageSubresourceRange range = imageInfo.FullRange();
    range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
    state.RecordUse(range, eFrameRef_Read, 0);

    CheckSubresourceRanges(state, true, false, false, false);
    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      if(it->range().aspectMask == VK_IMAGE_ASPECT_DEPTH_BIT)
        CheckSubresourceState(it->state(), readSubstate);
      else
        CheckSubresourceState(it->state(), initSubstate);
    }
  };

  SECTION("Split mip levels")
  {
    ImageState state(image, imageInfo, eFrameRef_None);
    ImageSubresourceRange range = imageInfo.FullRange();
    range.baseMipLevel = 1;
    range.levelCount = 3;
    state.RecordUse(range, eFrameRef_Read, 0);

    CheckSubresourceRanges(state, false, true, false, false);
    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      if(it->range().baseMipLevel >= range.baseMipLevel &&
         it->range().baseMipLevel - range.baseMipLevel < range.levelCount)
        CheckSubresourceState(it->state(), readSubstate);
      else
        CheckSubresourceState(it->state(), initSubstate);
    }
  };

  SECTION("Split array layers")
  {
    ImageState state(image, imageInfo, eFrameRef_None);
    ImageSubresourceRange range = imageInfo.FullRange();
    range.baseArrayLayer = 3;
    range.layerCount = 5;
    state.RecordUse(range, eFrameRef_Read, 0);

    CheckSubresourceRanges(state, false, false, true, false);
    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      if(it->range().baseArrayLayer >= range.baseArrayLayer &&
         it->range().baseArrayLayer - range.baseArrayLayer < range.layerCount)
        CheckSubresourceState(it->state(), readSubstate);
      else
        CheckSubresourceState(it->state(), initSubstate);
    }
  };

  SECTION("Split depth slices")
  {
    ImageState state(image, imageInfo, eFrameRef_None);
    ImageSubresourceRange range = imageInfo.FullRange();
    range.baseDepthSlice = 1;
    range.sliceCount = 1;
    state.RecordUse(range, eFrameRef_Read, 0);

    CheckSubresourceRanges(state, false, false, false, true);
    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      if(it->range().baseDepthSlice >= range.baseDepthSlice &&
         it->range().baseDepthSlice - range.baseDepthSlice < range.sliceCount)
        CheckSubresourceState(it->state(), readSubstate);
      else
        CheckSubresourceState(it->state(), initSubstate);
    }
  };

  SECTION("Split aspect to depth")
  {
    ImageState state(image, imageInfo, eFrameRef_None);

    ImageSubresourceRange aspectRange(imageInfo.FullRange());
    aspectRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
    state.RecordUse(aspectRange, eFrameRef_Read, 0);
    CheckSubresourceRanges(state, true, false, false, false);

    ImageSubresourceRange levelRange(imageInfo.FullRange());
    levelRange.baseMipLevel = 0;
    levelRange.levelCount = 1;
    state.RecordUse(levelRange, eFrameRef_PartialWrite, 1);
    CheckSubresourceRanges(state, true, true, false, false);

    ImageSubresourceRange layerRange(imageInfo.FullRange());
    layerRange.baseArrayLayer = 0;
    layerRange.layerCount = 1;
    state.RecordUse(layerRange, eFrameRef_Read, 2);
    CheckSubresourceRanges(state, true, true, true, false);

    ImageSubresourceRange sliceRange(imageInfo.FullRange());
    sliceRange.baseDepthSlice = 0;
    sliceRange.sliceCount = 1;
    state.RecordUse(sliceRange, eFrameRef_CompleteWrite, 3);
    CheckSubresourceRanges(state, true, true, true, true);

    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      ImageSubresourceState substate(initSubstate);
      if(it->range().aspectMask == VK_IMAGE_ASPECT_DEPTH_BIT)
      {
        substate.refType = ComposeFrameRefs(substate.refType, eFrameRef_Read);
        substate.newQueueFamilyIndex = 0;
        if(substate.oldQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
          substate.oldQueueFamilyIndex = substate.newQueueFamilyIndex;
      }
      if(levelRange.baseMipLevel <= it->range().baseMipLevel &&
         it->range().baseMipLevel - levelRange.baseMipLevel < levelRange.levelCount)
      {
        substate.refType = ComposeFrameRefs(substate.refType, eFrameRef_PartialWrite);
        substate.newQueueFamilyIndex = 1;
        if(substate.oldQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
          substate.oldQueueFamilyIndex = substate.newQueueFamilyIndex;
      }
      if(layerRange.baseArrayLayer <= it->range().baseArrayLayer &&
         it->range().baseArrayLayer - layerRange.baseArrayLayer < layerRange.layerCount)
      {
        substate.refType = ComposeFrameRefs(substate.refType, eFrameRef_Read);
        substate.newQueueFamilyIndex = 2;
        if(substate.oldQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
          substate.oldQueueFamilyIndex = substate.newQueueFamilyIndex;
      }
      if(sliceRange.baseDepthSlice <= it->range().baseDepthSlice &&
         it->range().baseDepthSlice - sliceRange.baseDepthSlice < sliceRange.sliceCount)
      {
        substate.refType = ComposeFrameRefs(substate.refType, eFrameRef_CompleteWrite);
        substate.newQueueFamilyIndex = 3;
        if(substate.oldQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
          substate.oldQueueFamilyIndex = substate.newQueueFamilyIndex;
      }

      CheckSubresourceState(it->state(), substate);
    }
  };

  SECTION("Split depth to aspect")
  {
    ImageState state(image, imageInfo, eFrameRef_None);

    ImageSubresourceRange sliceRange(imageInfo.FullRange());
    sliceRange.baseDepthSlice = 0;
    sliceRange.sliceCount = 1;
    state.RecordUse(sliceRange, eFrameRef_CompleteWrite, 3);
    CheckSubresourceRanges(state, false, false, false, true);

    ImageSubresourceRange layerRange(imageInfo.FullRange());
    layerRange.baseArrayLayer = 0;
    layerRange.layerCount = 1;
    state.RecordUse(layerRange, eFrameRef_Read, 2);
    CheckSubresourceRanges(state, false, false, true, true);

    ImageSubresourceRange levelRange(imageInfo.FullRange());
    levelRange.baseMipLevel = 0;
    levelRange.levelCount = 1;
    state.RecordUse(levelRange, eFrameRef_PartialWrite, 1);
    CheckSubresourceRanges(state, false, true, true, true);

    ImageSubresourceRange aspectRange(imageInfo.FullRange());
    aspectRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
    state.RecordUse(aspectRange, eFrameRef_Read, 0);
    CheckSubresourceRanges(state, true, true, true, true);

    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      ImageSubresourceState substate(initSubstate);
      if(sliceRange.baseDepthSlice <= it->range().baseDepthSlice &&
         it->range().baseDepthSlice - sliceRange.baseDepthSlice < sliceRange.sliceCount)
      {
        substate.refType = ComposeFrameRefs(substate.refType, eFrameRef_CompleteWrite);
        substate.newQueueFamilyIndex = 3;
        if(substate.oldQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
          substate.oldQueueFamilyIndex = substate.newQueueFamilyIndex;
      }
      if(layerRange.baseArrayLayer <= it->range().baseArrayLayer &&
         it->range().baseArrayLayer - layerRange.baseArrayLayer < layerRange.layerCount)
      {
        substate.refType = ComposeFrameRefs(substate.refType, eFrameRef_Read);
        substate.newQueueFamilyIndex = 2;
        if(substate.oldQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
          substate.oldQueueFamilyIndex = substate.newQueueFamilyIndex;
      }
      if(levelRange.baseMipLevel <= it->range().baseMipLevel &&
         it->range().baseMipLevel - levelRange.baseMipLevel < levelRange.levelCount)
      {
        substate.refType = ComposeFrameRefs(substate.refType, eFrameRef_PartialWrite);
        substate.newQueueFamilyIndex = 1;
        if(substate.oldQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
          substate.oldQueueFamilyIndex = substate.newQueueFamilyIndex;
      }
      if(it->range().aspectMask == VK_IMAGE_ASPECT_DEPTH_BIT)
      {
        substate.refType = ComposeFrameRefs(substate.refType, eFrameRef_Read);
        substate.newQueueFamilyIndex = 0;
        if(substate.oldQueueFamilyIndex == VK_QUEUE_FAMILY_IGNORED)
          substate.oldQueueFamilyIndex = substate.newQueueFamilyIndex;
      }

      CheckSubresourceState(it->state(), substate);
    }
  };

  SECTION("Single barrier")
  {
    ImageState state(image, imageInfo, eFrameRef_None);
    ImageSubresourceRange range(imageInfo.FullRange());
    range.baseArrayLayer = 1;
    range.layerCount = 1;

    VkImageMemoryBarrier barrier = {
        /* sType = */ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        /* pNext = */ NULL,
        /* srcAccessMask = */ 0,
        /* dstAccessMask = */ 0,
        /* oldLayout = */ VK_IMAGE_LAYOUT_UNDEFINED,
        /* newLayout = */ VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
        /* srcQueueFamilyIndex = */ 0,
        /* dstQueueFamilyIndex = */ 0,
        /* image = */ image,
        /* subresourceRange = */ range,
    };
    state.RecordBarrier(barrier, 0, transitionInfo);
    CheckSubresourceRanges(state, false, false, true, false);

    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      ImageSubresourceState substate(initSubstate);
      if(range.baseArrayLayer <= it->range().baseArrayLayer &&
         it->range().baseArrayLayer - range.baseArrayLayer < range.layerCount)
      {
        substate.oldQueueFamilyIndex = substate.newQueueFamilyIndex = 0;
        substate.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        substate.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
      }

      CheckSubresourceState(it->state(), substate);
    }
  };

  SECTION("Layout barriers")
  {
    ImageState state(image, imageInfo, eFrameRef_None);
    ImageSubresourceRange range(imageInfo.FullRange());
    range.baseArrayLayer = 0;
    range.layerCount = 1;

    VkImageMemoryBarrier barrier = {
        /* sType = */ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        /* pNext = */ NULL,
        /* srcAccessMask = */ 0,
        /* dstAccessMask = */ 0,
        /* oldLayout = */ VK_IMAGE_LAYOUT_UNDEFINED,
        /* newLayout = */ VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
        /* srcQueueFamilyIndex = */ 0,
        /* dstQueueFamilyIndex = */ 0,
        /* image = */ image,
        /* subresourceRange = */ range,
    };
    state.RecordBarrier(barrier, 0, transitionInfo);
    CheckSubresourceRanges(state, false, false, true, false);

    barrier.subresourceRange.baseArrayLayer = 1;
    state.RecordBarrier(barrier, 0, transitionInfo);
    CheckSubresourceRanges(state, false, false, true, false);

    barrier.subresourceRange.baseArrayLayer = range.baseArrayLayer = 0;
    barrier.subresourceRange.layerCount = range.layerCount = 2;
    barrier.oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
    barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
    state.RecordBarrier(barrier, 0, transitionInfo);
    CheckSubresourceRanges(state, false, false, true, false);

    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      ImageSubresourceState substate(initSubstate);
      if(range.baseArrayLayer <= it->range().baseArrayLayer &&
         it->range().baseArrayLayer - range.baseArrayLayer < range.layerCount)
      {
        substate.oldQueueFamilyIndex = substate.newQueueFamilyIndex = 0;
        substate.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        substate.newLayout = VK_IMAGE_LAYOUT_GENERAL;
      }

      CheckSubresourceState(it->state(), substate);
    }
  };

  SECTION("Unmatched queue family acquire")
  {
    ImageState state(image, imageInfo, eFrameRef_None);
    ImageSubresourceRange range(imageInfo.FullRange());
    range.baseArrayLayer = 1;
    range.layerCount = 2;

    VkImageMemoryBarrier barrier = {
        /* sType = */ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        /* pNext = */ NULL,
        /* srcAccessMask = */ 0,
        /* dstAccessMask = */ 0,
        /* oldLayout = */ VK_IMAGE_LAYOUT_GENERAL,
        /* newLayout = */ VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
        /* srcQueueFamilyIndex = */ 0,
        /* dstQueueFamilyIndex = */ 1,
        /* image = */ image,
        /* subresourceRange = */ range,
    };
    state.RecordBarrier(barrier, 1, transitionInfo);
    CheckSubresourceRanges(state, false, false, true, false);

    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      ImageSubresourceState substate(initSubstate);
      if(range.baseArrayLayer <= it->range().baseArrayLayer &&
         it->range().baseArrayLayer - range.baseArrayLayer < range.layerCount)
      {
        substate.oldQueueFamilyIndex = 0;
        substate.newQueueFamilyIndex = 1;
        substate.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
        substate.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
      }

      CheckSubresourceState(it->state(), substate);
    }

    REQUIRE(state.oldQueueFamilyTransfers.size() == 1);
    CHECK(state.oldQueueFamilyTransfers[0].oldLayout == VK_IMAGE_LAYOUT_GENERAL);
    CHECK(state.oldQueueFamilyTransfers[0].newLayout ==
          VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
    CHECK(state.oldQueueFamilyTransfers[0].srcQueueFamilyIndex == 0);
    CHECK(state.oldQueueFamilyTransfers[0].dstQueueFamilyIndex == 1);

    CHECK(state.newQueueFamilyTransfers.size() == 0);
  };

  SECTION("Unmatched queue family release")
  {
    ImageState state(image, imageInfo, eFrameRef_None);
    ImageSubresourceRange range(imageInfo.FullRange());
    range.baseArrayLayer = 1;
    range.layerCount = 2;

    VkImageMemoryBarrier barrier = {
        /* sType = */ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        /* pNext = */ NULL,
        /* srcAccessMask = */ 0,
        /* dstAccessMask = */ 0,
        /* oldLayout = */ VK_IMAGE_LAYOUT_GENERAL,
        /* newLayout = */ VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
        /* srcQueueFamilyIndex = */ 0,
        /* dstQueueFamilyIndex = */ 1,
        /* image = */ image,
        /* subresourceRange = */ range,
    };
    state.RecordBarrier(barrier, 0, transitionInfo);
    CheckSubresourceRanges(state, false, false, false, false);
    CheckSubresourceState(state.subresourceStates.begin()->state(), initSubstate);

    REQUIRE(state.newQueueFamilyTransfers.size() == 1);
    CHECK(state.newQueueFamilyTransfers[0].oldLayout == VK_IMAGE_LAYOUT_GENERAL);
    CHECK(state.newQueueFamilyTransfers[0].newLayout ==
          VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
    CHECK(state.newQueueFamilyTransfers[0].srcQueueFamilyIndex == 0);
    CHECK(state.newQueueFamilyTransfers[0].dstQueueFamilyIndex == 1);

    CHECK(state.oldQueueFamilyTransfers.size() == 0);
  };

  SECTION("Matched queue family transfer")
  {
    ImageState state(image, imageInfo, eFrameRef_None);
    ImageSubresourceRange range(imageInfo.FullRange());
    range.baseArrayLayer = 1;
    range.layerCount = 2;

    VkImageMemoryBarrier barrier = {
        /* sType = */ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        /* pNext = */ NULL,
        /* srcAccessMask = */ 0,
        /* dstAccessMask = */ 0,
        /* oldLayout = */ VK_IMAGE_LAYOUT_GENERAL,
        /* newLayout = */ VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
        /* srcQueueFamilyIndex = */ 0,
        /* dstQueueFamilyIndex = */ 1,
        /* image = */ image,
        /* subresourceRange = */ range,
    };
    state.RecordBarrier(barrier, 0, transitionInfo);
    CheckSubresourceRanges(state, false, false, false, false);
    state.RecordBarrier(barrier, 1, transitionInfo);
    CheckSubresourceRanges(state, false, false, true, false);

    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      ImageSubresourceState substate(initSubstate);
      if(range.baseArrayLayer <= it->range().baseArrayLayer &&
         it->range().baseArrayLayer - range.baseArrayLayer < range.layerCount)
      {
        substate.oldQueueFamilyIndex = 0;
        substate.newQueueFamilyIndex = 1;
        substate.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
        substate.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
      }

      CheckSubresourceState(it->state(), substate);
    }

    CHECK(state.oldQueueFamilyTransfers.size() == 0);

    CHECK(state.newQueueFamilyTransfers.size() == 0);
  };

  SECTION("Unsplit aspects")
  {
    ImageState state(image, imageInfo, eFrameRef_None);

    // read subresource, triggering a split in every dimension except depth
    ImageSubresourceRange range0 = imageInfo.FullRange();
    range0.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
    range0.baseMipLevel = 1;
    range0.levelCount = imageInfo.levelCount - 1;
    range0.baseArrayLayer = 1;
    range0.layerCount = imageInfo.layerCount - 1;
    range0.baseDepthSlice = 0;
    range0.sliceCount = imageInfo.extent.depth;
    state.RecordUse(range0, eFrameRef_Read, 0);

    // read all aspects
    ImageSubresourceRange range1 = range0;
    range1.aspectMask = imageInfo.Aspects();
    state.RecordUse(range1, eFrameRef_Read, 0);

    state.subresourceStates.Unsplit();

    CheckSubresourceRanges(state, false, true, true, false);
    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      if(it->range().baseMipLevel > 0 && it->range().baseArrayLayer > 0)
        CheckSubresourceState(it->state(), readSubstate);
      else
        CheckSubresourceState(it->state(), initSubstate);
    }
  };

  SECTION("Unsplit mip levels")
  {
    ImageState state(image, imageInfo, eFrameRef_None);

    // read subresource, triggering a split in every dimension except aspect
    ImageSubresourceRange range0 = imageInfo.FullRange();
    range0.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
    range0.baseMipLevel = 1;
    range0.levelCount = imageInfo.levelCount - 1;
    range0.baseArrayLayer = 1;
    range0.layerCount = imageInfo.layerCount - 1;
    range0.baseDepthSlice = 1;
    range0.sliceCount = imageInfo.extent.depth - 1;
    state.RecordUse(range0, eFrameRef_Read, 0);

    // read all mip levels
    ImageSubresourceRange range1 = range0;
    range1.baseMipLevel = 0;
    range1.levelCount = imageInfo.levelCount;
    state.RecordUse(range1, eFrameRef_Read, 0);

    state.subresourceStates.Unsplit();

    CheckSubresourceRanges(state, false, false, true, true);
    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      if(it->range().baseArrayLayer > 0 && it->range().baseDepthSlice > 0)
        CheckSubresourceState(it->state(), readSubstate);
      else
        CheckSubresourceState(it->state(), initSubstate);
    }
  };

  SECTION("Unsplit array layers")
  {
    ImageState state(image, imageInfo, eFrameRef_None);

    // read subresource, triggering a split in every dimension except mip levels
    ImageSubresourceRange range0 = imageInfo.FullRange();
    range0.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
    range0.baseMipLevel = 0;
    range0.levelCount = imageInfo.levelCount;
    range0.baseArrayLayer = 1;
    range0.layerCount = imageInfo.layerCount - 1;
    range0.baseDepthSlice = 1;
    range0.sliceCount = imageInfo.extent.depth - 1;
    state.RecordUse(range0, eFrameRef_Read, 0);

    // read all array layers
    ImageSubresourceRange range1 = range0;
    range1.baseArrayLayer = 0;
    range1.layerCount = imageInfo.layerCount;
    state.RecordUse(range1, eFrameRef_Read, 0);

    state.subresourceStates.Unsplit();

    CheckSubresourceRanges(state, true, false, false, true);
    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      if(it->range().aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT && it->range().baseDepthSlice > 0)
        CheckSubresourceState(it->state(), readSubstate);
      else
        CheckSubresourceState(it->state(), initSubstate);
    }
  };

  SECTION("Unsplit depth slices")
  {
    ImageState state(image, imageInfo, eFrameRef_None);

    // read subresource, triggering a split in every dimension except array layers
    ImageSubresourceRange range0 = imageInfo.FullRange();
    range0.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
    range0.baseMipLevel = 1;
    range0.levelCount = imageInfo.levelCount - 1;
    range0.baseArrayLayer = 0;
    range0.layerCount = imageInfo.layerCount;
    range0.baseDepthSlice = 1;
    range0.sliceCount = imageInfo.extent.depth - 1;
    state.RecordUse(range0, eFrameRef_Read, 0);

    // read all depth slices
    ImageSubresourceRange range1 = range0;
    range1.baseDepthSlice = 0;
    range1.sliceCount = imageInfo.extent.depth;
    state.RecordUse(range1, eFrameRef_Read, 0);

    state.subresourceStates.Unsplit();

    CheckSubresourceRanges(state, true, true, false, false);
    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      if(it->range().aspectMask == VK_IMAGE_ASPECT_STENCIL_BIT && it->range().baseMipLevel > 0)
        CheckSubresourceState(it->state(), readSubstate);
      else
        CheckSubresourceState(it->state(), initSubstate);
    }
  };

  SECTION("Unsplit all")
  {
    ImageState state(image, imageInfo, eFrameRef_None);

    // read subresource, triggering a split in every dimension
    ImageSubresourceRange range0 = imageInfo.FullRange();
    range0.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
    range0.baseMipLevel = 1;
    range0.levelCount = imageInfo.levelCount - 1;
    range0.baseArrayLayer = 1;
    range0.layerCount = imageInfo.layerCount - 1;
    range0.baseDepthSlice = 1;
    range0.sliceCount = imageInfo.extent.depth - 1;
    state.RecordUse(range0, eFrameRef_Read, 0);

    // read all subresources
    ImageSubresourceRange range1 = imageInfo.FullRange();
    state.RecordUse(range1, eFrameRef_Read, 0);

    state.subresourceStates.Unsplit();

    CheckSubresourceRanges(state, false, false, false, false);
    for(auto it = state.subresourceStates.begin(); it != state.subresourceStates.end(); ++it)
    {
      CheckSubresourceState(it->state(), readSubstate);
    }
  };
};

#endif    // ENABLED(ENABLE_UNIT_TESTS)
