From a6720755938d2669528024a9a33d00cd6731395e Mon Sep 17 00:00:00 2001 From: Ricardo Garcia Date: Tue, 10 Nov 2020 12:06:15 +0100 Subject: [PATCH] Test dynamic indexing of ray queries Create a new test that indexes ray queries in an array using an index that is dynamic, changing for each invocation with different values obtained from an SSBO. New tests: dEQP-VK.ray_query.misc.dynamic_indexing Components: Vulkan VK-GL-CTS issue: 2587 Change-Id: I2e53061ab606a86eb9985ce89b2fd44807aa3b45 --- AndroidGen.mk | 1 + android/cts/master/vk-master-2020-03-01.txt | 1 + android/cts/master/vk-master.txt | 1 + .../modules/vulkan/ray_query/CMakeLists.txt | 2 + .../vulkan/ray_query/vktRayQueryMiscTests.cpp | 360 +++++++++++++++++++++ .../vulkan/ray_query/vktRayQueryMiscTests.hpp | 40 +++ .../modules/vulkan/ray_query/vktRayQueryTests.cpp | 2 + external/vulkancts/mustpass/master/vk-default.txt | 1 + 8 files changed, 408 insertions(+) create mode 100644 external/vulkancts/modules/vulkan/ray_query/vktRayQueryMiscTests.cpp create mode 100644 external/vulkancts/modules/vulkan/ray_query/vktRayQueryMiscTests.hpp diff --git a/AndroidGen.mk b/AndroidGen.mk index 82dafdb..41ac93f 100644 --- a/AndroidGen.mk +++ b/AndroidGen.mk @@ -277,6 +277,7 @@ LOCAL_SRC_FILES := \ external/vulkancts/modules/vulkan/ray_query/vktRayQueryAccelerationStructuresTests.cpp \ external/vulkancts/modules/vulkan/ray_query/vktRayQueryBuiltinTests.cpp \ external/vulkancts/modules/vulkan/ray_query/vktRayQueryCullRayFlagsTests.cpp \ + external/vulkancts/modules/vulkan/ray_query/vktRayQueryMiscTests.cpp \ external/vulkancts/modules/vulkan/ray_query/vktRayQueryProceduralGeometryTests.cpp \ external/vulkancts/modules/vulkan/ray_query/vktRayQueryTests.cpp \ external/vulkancts/modules/vulkan/ray_query/vktRayQueryTraversalControlTests.cpp \ diff --git a/android/cts/master/vk-master-2020-03-01.txt b/android/cts/master/vk-master-2020-03-01.txt index af7cfa8..176de6f 100644 --- a/android/cts/master/vk-master-2020-03-01.txt +++ b/android/cts/master/vk-master-2020-03-01.txt @@ -375334,3 +375334,4 @@ dEQP-VK.ray_query.ray_flags.call_shader.skip_geometry.triangles.skiptriangles dEQP-VK.ray_query.ray_flags.call_shader.skip_geometry.triangles.skipaabb dEQP-VK.ray_query.ray_flags.call_shader.skip_geometry.aabbs.skiptriangles dEQP-VK.ray_query.ray_flags.call_shader.skip_geometry.aabbs.skipaabb +dEQP-VK.ray_query.misc.dynamic_indexing diff --git a/android/cts/master/vk-master.txt b/android/cts/master/vk-master.txt index c5b2131..65e8af0 100644 --- a/android/cts/master/vk-master.txt +++ b/android/cts/master/vk-master.txt @@ -785527,3 +785527,4 @@ dEQP-VK.ray_query.ray_flags.call_shader.skip_geometry.triangles.skiptriangles dEQP-VK.ray_query.ray_flags.call_shader.skip_geometry.triangles.skipaabb dEQP-VK.ray_query.ray_flags.call_shader.skip_geometry.aabbs.skiptriangles dEQP-VK.ray_query.ray_flags.call_shader.skip_geometry.aabbs.skipaabb +dEQP-VK.ray_query.misc.dynamic_indexing diff --git a/external/vulkancts/modules/vulkan/ray_query/CMakeLists.txt b/external/vulkancts/modules/vulkan/ray_query/CMakeLists.txt index 29a5292..3ad976b 100644 --- a/external/vulkancts/modules/vulkan/ray_query/CMakeLists.txt +++ b/external/vulkancts/modules/vulkan/ray_query/CMakeLists.txt @@ -15,6 +15,8 @@ set(DEQP_VK_RAY_QUERY_SRCS vktRayQueryWatertightnessTests.hpp vktRayQueryCullRayFlagsTests.cpp vktRayQueryCullRayFlagsTests.hpp + vktRayQueryMiscTests.cpp + vktRayQueryMiscTests.hpp ) set(DEQP_VK_RAY_QUERY_LIBS diff --git a/external/vulkancts/modules/vulkan/ray_query/vktRayQueryMiscTests.cpp b/external/vulkancts/modules/vulkan/ray_query/vktRayQueryMiscTests.cpp new file mode 100644 index 0000000..b3ac5e2 --- /dev/null +++ b/external/vulkancts/modules/vulkan/ray_query/vktRayQueryMiscTests.cpp @@ -0,0 +1,360 @@ +/*------------------------------------------------------------------------- + * Vulkan Conformance Tests + * ------------------------ + * + * Copyright (c) 2020 The Khronos Group Inc. + * Copyright (c) 2020 Valve Corporation. + * + * 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. + * + *//*! + * \file + * \brief Ray Query miscellaneous tests + *//*--------------------------------------------------------------------*/ + +#include "vktRayQueryMiscTests.hpp" +#include "vktTestCase.hpp" + +#include "vkRayTracingUtil.hpp" +#include "vkBufferWithMemory.hpp" +#include "vkObjUtil.hpp" +#include "vkBuilderUtil.hpp" +#include "vkTypeUtil.hpp" +#include "vkCmdUtil.hpp" +#include "vkBarrierUtil.hpp" + +#include "tcuVector.hpp" + +#include "deUniquePtr.hpp" +#include "deRandom.hpp" + +#include +#include +#include + +namespace vkt +{ +namespace RayQuery +{ + +namespace +{ + +using namespace vk; + +class DynamicIndexingCase : public vkt::TestCase +{ +public: + DynamicIndexingCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description); + virtual ~DynamicIndexingCase (void) {} + + virtual void initPrograms (vk::SourceCollections& programCollection) const override; + virtual void checkSupport (Context& context) const override; + virtual TestInstance* createInstance (Context& context) const override; + + // Constants and data types. + static constexpr deUint32 kLocalSizeX = 128u; + static constexpr deUint32 kNumQueries = 128u; + + // This must match the shader. + struct InputData + { + deUint32 goodQueryIndex; + deUint32 proceedQueryIndex; + }; +}; + +class DynamicIndexingInstance : public vkt::TestInstance +{ +public: + DynamicIndexingInstance (Context& context); + virtual ~DynamicIndexingInstance (void) {} + + virtual tcu::TestStatus iterate (void); +}; + +DynamicIndexingCase::DynamicIndexingCase (tcu::TestContext& testCtx, const std::string& name, const std::string& description) + : vkt::TestCase (testCtx, name, description) +{} + +void DynamicIndexingCase::initPrograms (vk::SourceCollections& programCollection) const +{ + const vk::ShaderBuildOptions buildOptions(programCollection.usedVulkanVersion, vk::SPIRV_VERSION_1_4, 0u, true); + + std::ostringstream src; + + src + << "#version 460\n" + << "#extension GL_EXT_ray_query : require\n" + << "#extension GL_EXT_ray_tracing : require\n" + << "\n" + << "layout (local_size_x=" << kLocalSizeX << ", local_size_y=1, local_size_z=1) in; \n" + << "\n" + << "struct InputData {\n" + << " uint goodQueryIndex;\n" + << " uint proceedQueryIndex; // Note: same index as the one above in practice.\n" + << "};\n" + << "\n" + << "layout (set=0, binding=0) uniform accelerationStructureEXT topLevelAS;\n" + << "layout (set=0, binding=1, std430) buffer InputBlock {\n" + << " InputData inputData[];\n" + << "} inputBlock;\n" + << "layout (set=0, binding=2, std430) buffer OutputBlock {\n" + << " uint outputData[];\n" + << "} outputBlock;\n" + << "\n" + << "void main()\n" + << "{\n" + << " const uint numQueries = " << kNumQueries << ";\n" + << "\n" + << " const uint rayFlags = 0u; \n" + << " const uint cullMask = 0xFFu;\n" + << " const float tmin = 0.1;\n" + << " const float tmax = 10.0;\n" + << " const vec3 direct = vec3(0, 0, 1); \n" + << "\n" + << " rayQueryEXT rayQueries[numQueries];\n" + << " vec3 origin;\n" + << "\n" + << " InputData inputValues = inputBlock.inputData[gl_LocalInvocationID.x];\n" + << "\n" + << " // Initialize all queries. Only goodQueryIndex will have the right origin for a hit.\n" + << " for (int i = 0; i < numQueries; i++) {\n" + << " origin = ((i == inputValues.goodQueryIndex) ? vec3(0, 0, 0) : vec3(5, 5, 0));\n" + << " rayQueryInitializeEXT(rayQueries[i], topLevelAS, rayFlags, cullMask, origin, tmin, direct, tmax);\n" + << " }\n" + << "\n" + << " // Attempt to proceed with the good query to confirm a hit.\n" + << " while (rayQueryProceedEXT(rayQueries[inputValues.proceedQueryIndex]))\n" + << " outputBlock.outputData[gl_LocalInvocationID.x] = 1u; \n" + << "}\n" + ; + + programCollection.glslSources.add("comp") << glu::ComputeSource(updateRayTracingGLSL(src.str())) << buildOptions; +} + +void DynamicIndexingCase::checkSupport (Context& context) const +{ + context.requireDeviceFunctionality("VK_KHR_acceleration_structure"); + context.requireDeviceFunctionality("VK_KHR_ray_query"); + + const auto& rayQueryFeaturesKHR = context.getRayQueryFeatures(); + if (!rayQueryFeaturesKHR.rayQuery) + TCU_THROW(NotSupportedError, "Ray queries not supported"); + + const auto& accelerationStructureFeaturesKHR = context.getAccelerationStructureFeatures(); + if (!accelerationStructureFeaturesKHR.accelerationStructure) + TCU_FAIL("Acceleration structures not supported but ray queries supported"); +} + +vkt::TestInstance* DynamicIndexingCase::createInstance (Context& context) const +{ + return new DynamicIndexingInstance(context); +} + +DynamicIndexingInstance::DynamicIndexingInstance (Context& context) + : vkt::TestInstance(context) +{} + +deUint32 getRndIndex (de::Random& rng, deUint32 size) +{ + DE_ASSERT(size > 0u); + DE_ASSERT(size <= static_cast(std::numeric_limits::max())); + + const int iMin = 0; + const int iMax = static_cast(size) - 1; + + return static_cast(rng.getInt(iMin, iMax)); +} + +tcu::TestStatus DynamicIndexingInstance::iterate (void) +{ + using InputData = DynamicIndexingCase::InputData; + constexpr auto kLocalSizeX = DynamicIndexingCase::kLocalSizeX; + constexpr auto kNumQueries = DynamicIndexingCase::kNumQueries; + + const auto& vkd = m_context.getDeviceInterface(); + const auto device = m_context.getDevice(); + auto& alloc = m_context.getDefaultAllocator(); + const auto queue = m_context.getUniversalQueue(); + const auto qIndex = m_context.getUniversalQueueFamilyIndex(); + + de::Random rng (1604936737u); + InputData inputDataArray[kLocalSizeX]; + deUint32 outputDataArray[kLocalSizeX]; + + // Prepare input buffer. + for (int i = 0; i < DE_LENGTH_OF_ARRAY(inputDataArray); ++i) + { + // The two values will contain the same query index. + inputDataArray[i].goodQueryIndex = getRndIndex(rng, kNumQueries); + inputDataArray[i].proceedQueryIndex = inputDataArray[i].goodQueryIndex; + } + + const auto inputBufferSize = static_cast(sizeof(inputDataArray)); + const auto inputBufferInfo = makeBufferCreateInfo(inputBufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + BufferWithMemory inputBuffer (vkd, device, alloc, inputBufferInfo, MemoryRequirement::HostVisible); + auto& inputBufferAlloc = inputBuffer.getAllocation(); + void* inputBufferPtr = inputBufferAlloc.getHostPtr(); + + deMemcpy(inputBufferPtr, inputDataArray, static_cast(inputBufferSize)); + flushAlloc(vkd, device, inputBufferAlloc); + + // Prepare output buffer. + const auto outputBufferSize = static_cast(sizeof(outputDataArray)); + const auto outputBufferInfo = makeBufferCreateInfo(outputBufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + BufferWithMemory outputBuffer (vkd, device, alloc, outputBufferInfo, MemoryRequirement::HostVisible); + auto& outputBufferAlloc = outputBuffer.getAllocation(); + void* outputBufferPtr = outputBufferAlloc.getHostPtr(); + + deMemset(outputBufferPtr, 0, static_cast(outputBufferSize)); + flushAlloc(vkd, device, outputBufferAlloc); + + // Prepare acceleration structures. + const auto cmdPool = makeCommandPool(vkd, device, qIndex); + const auto cmdBufferPtr = allocateCommandBuffer(vkd, device, cmdPool.get(), VK_COMMAND_BUFFER_LEVEL_PRIMARY); + const auto cmdBuffer = cmdBufferPtr.get(); + beginCommandBuffer(vkd, cmdBuffer); + + de::SharedPtr topLevelAS (makeTopLevelAccelerationStructure().release()); + de::SharedPtr bottomLevelAS (makeBottomLevelAccelerationStructure().release()); + + // These need to match the origin and direction in the shader for a hit. + const std::vector vertices = + { + tcu::Vec3(-1.0f, -1.0f, 1.0f), + tcu::Vec3(-1.0f, 1.0f, 1.0f), + tcu::Vec3( 1.0f, -1.0f, 1.0f), + + tcu::Vec3(-1.0f, 1.0f, 1.0f), + tcu::Vec3( 1.0f, 1.0f, 1.0f), + tcu::Vec3( 1.0f, -1.0f, 1.0f), + }; + + bottomLevelAS->addGeometry(vertices, /*triangles*/true, VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR); + bottomLevelAS->createAndBuild(vkd, device, cmdBuffer, alloc); + + topLevelAS->addInstance(bottomLevelAS); + topLevelAS->createAndBuild(vkd, device, cmdBuffer, alloc); + + // Descriptor set layout. + const VkShaderStageFlagBits stageBit = VK_SHADER_STAGE_COMPUTE_BIT; + + DescriptorSetLayoutBuilder layoutBuilder; + layoutBuilder.addSingleBinding(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, stageBit); + layoutBuilder.addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, stageBit); + layoutBuilder.addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, stageBit); + const auto descriptorSetLayout = layoutBuilder.build(vkd, device); + + // Shader module. + const auto shaderModule = createShaderModule(vkd, device, m_context.getBinaryCollection().get("comp"), 0u); + + // Pipeline layout. + const auto pipelineLayout = makePipelineLayout(vkd, device, descriptorSetLayout.get()); + + const VkPipelineShaderStageCreateInfo shaderStageInfo = + { + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, // VkStructureType sType; + nullptr, // const void* pNext; + 0u, // VkPipelineShaderStageCreateFlags flags; + stageBit, // VkShaderStageFlagBits stage; + shaderModule.get(), // VkShaderModule module; + "main", // const char* pName; + nullptr, // const VkSpecializationInfo* pSpecializationInfo; + }; + + const VkComputePipelineCreateInfo pipelineInfo = + { + VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, // VkStructureType sType; + nullptr, // const void* pNext; + 0u, // VkPipelineCreateFlags flags; + shaderStageInfo, // VkPipelineShaderStageCreateInfo stage; + pipelineLayout.get(), // VkPipelineLayout layout; + DE_NULL, // VkPipeline basePipelineHandle; + 0, // deInt32 basePipelineIndex; + }; + + const auto pipeline = createComputePipeline(vkd, device, DE_NULL, &pipelineInfo); + + // Create and update descriptor set. + DescriptorPoolBuilder poolBuilder; + poolBuilder.addType(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR); + poolBuilder.addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2u); + + const auto descriptorPool = poolBuilder.build(vkd, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u); + const auto descriptorSetPtr = makeDescriptorSet(vkd, device, descriptorPool.get(), descriptorSetLayout.get()); + const auto descriptorSet = descriptorSetPtr.get(); + + const VkWriteDescriptorSetAccelerationStructureKHR asWrite = + { + VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR, // VkStructureType sType; + nullptr, // const void* pNext; + 1u, // deUint32 accelerationStructureCount; + topLevelAS->getPtr(), // const VkAccelerationStructureKHR* pAccelerationStructures; + }; + + const auto inputBufferWriteInfo = makeDescriptorBufferInfo(inputBuffer.get(), 0ull, inputBufferSize); + const auto outputBufferWriteInfo = makeDescriptorBufferInfo(outputBuffer.get(), 0ull, outputBufferSize); + + DescriptorSetUpdateBuilder updateBuilder; + updateBuilder.writeSingle(descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u), VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, &asWrite); + updateBuilder.writeSingle(descriptorSet, DescriptorSetUpdateBuilder::Location::binding(1u), VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &inputBufferWriteInfo); + updateBuilder.writeSingle(descriptorSet, DescriptorSetUpdateBuilder::Location::binding(2u), VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &outputBufferWriteInfo); + updateBuilder.update(vkd, device); + + // Use pipeline. + vkd.cmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.get()); + vkd.cmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout.get(), 0u, 1u, &descriptorSet, 0u, nullptr); + vkd.cmdDispatch(cmdBuffer, 1u, 1u, 1u); + + const auto memBarrier = makeMemoryBarrier(VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); + vkd.cmdPipelineBarrier(cmdBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0u, 1u, &memBarrier, 0u, nullptr, 0u, nullptr); + + // Submit recorded commands. + endCommandBuffer(vkd, cmdBuffer); + submitCommandsAndWait(vkd, device, queue, cmdBuffer); + + // Check output buffer. + invalidateAlloc(vkd, device, outputBufferAlloc); + deMemcpy(outputDataArray, outputBufferPtr, static_cast(outputBufferSize)); + + for (int i = 0; i < DE_LENGTH_OF_ARRAY(outputDataArray); ++i) + { + constexpr auto expected = 1u; + const auto& value = outputDataArray[i]; + + if (value != expected) + { + std::ostringstream msg; + msg << "Unexpected value found at position " << i << " in the output buffer: expected " << expected << " but found " << value; + TCU_FAIL(msg.str()); + } + } + + return tcu::TestStatus::pass("Pass"); +} + +} // anonymous + +tcu::TestCaseGroup* createMiscTests (tcu::TestContext& testCtx) +{ + de::MovePtr group (new tcu::TestCaseGroup(testCtx, "misc", "Miscellaneous ray query tests")); + + group->addChild(new DynamicIndexingCase(testCtx, "dynamic_indexing", "Dynamic indexing of ray queries")); + + return group.release(); +} + +} // RayQuery +} // vkt + diff --git a/external/vulkancts/modules/vulkan/ray_query/vktRayQueryMiscTests.hpp b/external/vulkancts/modules/vulkan/ray_query/vktRayQueryMiscTests.hpp new file mode 100644 index 0000000..16ea566 --- /dev/null +++ b/external/vulkancts/modules/vulkan/ray_query/vktRayQueryMiscTests.hpp @@ -0,0 +1,40 @@ +#ifndef _VKTRAYQUERYMISCTESTS_HPP +#define _VKTRAYQUERYMISCTESTS_HPP +/*------------------------------------------------------------------------- + * Vulkan Conformance Tests + * ------------------------ + * + * Copyright (c) 2020 The Khronos Group Inc. + * Copyright (c) 2020 Valve Corporation. + * + * 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. + * + *//*! + * \file + * \brief Ray Query miscellaneous tests + *//*--------------------------------------------------------------------*/ + +#include "tcuDefs.hpp" +#include "tcuTestCase.hpp" + +namespace vkt +{ +namespace RayQuery +{ + +tcu::TestCaseGroup* createMiscTests (tcu::TestContext& testCtx); + +} // RayQuery +} // vkt + +#endif // _VKTRAYQUERYMISCTESTS_HPP diff --git a/external/vulkancts/modules/vulkan/ray_query/vktRayQueryTests.cpp b/external/vulkancts/modules/vulkan/ray_query/vktRayQueryTests.cpp index ebe8592..0f20ac0 100644 --- a/external/vulkancts/modules/vulkan/ray_query/vktRayQueryTests.cpp +++ b/external/vulkancts/modules/vulkan/ray_query/vktRayQueryTests.cpp @@ -28,6 +28,7 @@ #include "vktRayQueryProceduralGeometryTests.hpp" #include "vktRayQueryWatertightnessTests.hpp" #include "vktRayQueryCullRayFlagsTests.hpp" +#include "vktRayQueryMiscTests.hpp" #include "deUniquePtr.hpp" @@ -49,6 +50,7 @@ tcu::TestCaseGroup* createTests (tcu::TestContext& testCtx) group->addChild(createAdvancedTests(testCtx)); group->addChild(createWatertightnessTests(testCtx)); group->addChild(createCullRayFlagsTests(testCtx)); + group->addChild(createMiscTests(testCtx)); return group.release(); } diff --git a/external/vulkancts/mustpass/master/vk-default.txt b/external/vulkancts/mustpass/master/vk-default.txt index 6e4e278..cdd6ef0 100644 --- a/external/vulkancts/mustpass/master/vk-default.txt +++ b/external/vulkancts/mustpass/master/vk-default.txt @@ -787975,3 +787975,4 @@ dEQP-VK.ray_query.ray_flags.call_shader.skip_geometry.triangles.skiptriangles dEQP-VK.ray_query.ray_flags.call_shader.skip_geometry.triangles.skipaabb dEQP-VK.ray_query.ray_flags.call_shader.skip_geometry.aabbs.skiptriangles dEQP-VK.ray_query.ray_flags.call_shader.skip_geometry.aabbs.skipaabb +dEQP-VK.ray_query.misc.dynamic_indexing -- 2.7.4