Invalid VkAccelerationStructureKHR with raii api when CMDBuildAS

need help as to why raii “device.createAccelerationStructureKHR(create_info)” is not working for me.

i got a AS helper class like the following, which is directly translated from non-raii version.

class vk_AccStruct_raii
{
public:
	vk_AccStruct_raii() = default;
	
	void build_bl(const vk::raii::Device& device, const vk::raii::PhysicalDevice& pDevice,
		const vk::raii::CommandPool& cmdPool, const vk::raii::Queue& queue,
		const vk::PhysicalDeviceAccelerationStructurePropertiesKHR* asProp,
		const std::vector<vk::AccelerationStructureGeometryKHR>& as_geo,
		const std::vector<std::vector<vk::AccelerationStructureBuildRangeInfoKHR>>& as_bdRanges,
		bool allowUpdate);

	void build_tl(const vk::raii::Device& device, const vk::raii::PhysicalDevice& pDevice,
		const vk::raii::CommandPool& cmdPool, const vk::raii::Queue& queue,
		const vk::PhysicalDeviceAccelerationStructurePropertiesKHR* asProp,
		const std::vector<vk::AccelerationStructureGeometryKHR>& as_geo,
		const std::vector<std::vector<vk::AccelerationStructureBuildRangeInfoKHR>>& as_bdRanges,
		bool allowUpdate);

	... ...
	
private:
	struct as_BuildSizes
	{
		as_BuildSizes() :
			accelerationStructureSize_offset(0), updateScratchSize_offset(0), buildScratchSize_offset(0),
			accelerationStructureSize(0), updateScratchSize(0), buildScratchSize(0) { }

		as_BuildSizes(
			vk::DeviceSize asSizeOffset, vk::DeviceSize updScrtachSizeOffset, vk::DeviceSize bScratchSizeOffset,
			vk::DeviceSize asSize, vk::DeviceSize updScratchSize, vk::DeviceSize bSratchSize) :
			accelerationStructureSize_offset(asSizeOffset), updateScratchSize_offset(updScrtachSizeOffset), buildScratchSize_offset(bScratchSizeOffset),
			accelerationStructureSize(asSize), updateScratchSize(updScratchSize), buildScratchSize(bSratchSize) { }

		vk::DeviceSize       accelerationStructureSize_offset;
		vk::DeviceSize       updateScratchSize_offset;
		vk::DeviceSize       buildScratchSize_offset;

		vk::DeviceSize       accelerationStructureSize;
		vk::DeviceSize       updateScratchSize;
		vk::DeviceSize       buildScratchSize;
	};

	std::vector<vk::AccelerationStructureKHR>	_as_Handles;
	vk::raii::Buffer							_as_buff			{ nullptr };
	vk::raii::DeviceMemory						_as_buff_DevMem		{ nullptr };
	/*
	 * 0 = not built,
	 * 1 = bottom level
	 * 2 = top level
	 */
	uint32_t									_buildState			{0};
};

build_xx method implement :

void vk_AccStruct_raii::build_bl(const vk::raii::Device& device, const vk::raii::PhysicalDevice& pDevice,
                                 const vk::raii::CommandPool& cmdPool, const vk::raii::Queue& queue,
                                 const vk::PhysicalDeviceAccelerationStructurePropertiesKHR* asProp,
                                 const std::vector<vk::AccelerationStructureGeometryKHR>& as_geo,
                                 const std::vector<std::vector<vk::AccelerationStructureBuildRangeInfoKHR>>& as_bdRanges, bool allowUpdate)
{
	if (_buildState > 0)return;
	
	TBVK_ASSERT(as_geo.size() == as_bdRanges.size(), "TriangleData & BuildRange Data Size Not Equal");
#if TBVK_DEBUG_ON
	for (const auto& data : as_geo)
		TBVK_ASSERT(data.geometryType == vk::GeometryTypeKHR::eTriangles, "[triPrimitiveData] Contained Non-Triangle Data");
#endif // TBVK_DEBUG_ON

	// Helper function to align a value to a given alignment
	const auto alignUp = [](auto value, size_t alignment) noexcept { return ((value + alignment - 1) & ~(alignment - 1)); };

	const auto geoSize = static_cast<uint32_t>(as_geo.size());

	// accumulate total sizes
	vk::DeviceSize accelerationStructureSize = 0;
	vk::DeviceSize updateScratchSize = 0;
	vk::DeviceSize buildScratchSize = 0;

	std::vector<vk::AccelerationStructureBuildGeometryInfoKHR> bd_geo_infos;
	bd_geo_infos.reserve(geoSize);

	std::vector<const vk::AccelerationStructureBuildRangeInfoKHR*> bd_ranges_list;
	bd_ranges_list.reserve(geoSize);

	std::vector<as_BuildSizes> geo_bd_sizes;
	geo_bd_sizes.reserve(geoSize);

	for (uint32_t i = 0; i < geoSize; ++i)
	{
		const auto& bRangeList = as_bdRanges[i];
		const auto bRangeCount = bRangeList.size(); // build range count : its SubMesh Count for triangle data (only meaningful context)
		uint32_t maxPrimCount = 0;
		for (uint32_t br = 0; br < bRangeCount; ++br)maxPrimCount += bRangeList[br].primitiveCount;
		bd_ranges_list.push_back(bRangeList.data());

		// only different in Build Info Struct
		auto buildInfo = TbUtils_Vk_Raii::as_BuildGeoInfo_BL(allowUpdate);
		buildInfo.geometryCount = bRangeCount; // is here telling how many BuildRange that this "VkAccelerationStructureGeometryKHR" has ???
		buildInfo.pGeometries = &as_geo[i];
		buildInfo.dstAccelerationStructure = VK_NULL_HANDLE;		// will fill in later
		buildInfo.scratchData = {};									// will fill in later
		bd_geo_infos.push_back(buildInfo);

		vk::AccelerationStructureBuildSizesInfoKHR q_build_size =
			device.getAccelerationStructureBuildSizesKHR(
				vk::AccelerationStructureBuildTypeKHR::eDevice,  // or vk::AccelerationStructureBuildTypeKHR::eHost if build_on_host is preferred
				buildInfo, { maxPrimCount });

		// diff from scratch. AS requires multiple of 256 size
		auto c_accelerationStructureSize = alignUp(q_build_size.accelerationStructureSize, 256u);
		auto c_updateScratchSize = alignUp(q_build_size.updateScratchSize, asProp->minAccelerationStructureScratchOffsetAlignment);
		auto c_buildScratchSize = alignUp(q_build_size.buildScratchSize, asProp->minAccelerationStructureScratchOffsetAlignment);

		geo_bd_sizes.emplace_back(
			accelerationStructureSize, updateScratchSize, buildScratchSize,			// offsets
			c_accelerationStructureSize, c_updateScratchSize, c_buildScratchSize	// sizes
		);

		#if TBVK_DEBUG_ON
		printf("BL::>accelerationStructure_Offset:[%llu] | updateScratch_Offset:[%llu] | buildScratch_Offset:[%llu]\n",
			accelerationStructureSize, updateScratchSize, buildScratchSize);
		printf("BL::>GeoIndex:[%u]Align:[%u] | accelerationStructure:[%llu]Align:[%llu] | updateScratch:[%llu]Align:[%llu] | buildScratch:[%llu]Align:[%llu]\n",
			i, asProp->minAccelerationStructureScratchOffsetAlignment,
			q_build_size.accelerationStructureSize, c_accelerationStructureSize,
			q_build_size.updateScratchSize, c_updateScratchSize,
			q_build_size.buildScratchSize, c_buildScratchSize
		);
		#endif // TBVK_DEBUG_ON

		accelerationStructureSize += c_accelerationStructureSize;
		updateScratchSize += c_updateScratchSize;
		buildScratchSize += c_buildScratchSize;

		#if TBVK_DEBUG_ON
		printf("BL::>Accumulated --> accelerationStructureSize:[%llu] | updateScratchSize:[%llu] | buildScratchSize:[%llu]\n",
			accelerationStructureSize, updateScratchSize, buildScratchSize);
		#endif // TBVK_DEBUG_ON
	}

	// create the actual buffer for the AS
	// create the actual AS buffer (BL)
	const vk::SharingMode sharingMode = vk::SharingMode::eExclusive;
	const vk::MemoryPropertyFlags memPropFlag = vk::MemoryPropertyFlagBits::eDeviceLocal;
	vk::MemoryAllocateFlagsInfoKHR flags_info{ };
	flags_info.flags = vk::MemoryAllocateFlagBits::eDeviceAddress;
	const vk::BufferUsageFlags usage =
		vk::BufferUsageFlagBits::eAccelerationStructureStorageKHR |
		vk::BufferUsageFlagBits::eStorageBuffer |
		vk::BufferUsageFlagBits::eShaderDeviceAddress;
	TbUtils_Vk_Raii::vkCreate_Buffer(device, pDevice,
		_as_buff, _as_buff_DevMem,
		accelerationStructureSize,
		usage, memPropFlag, sharingMode,
		nullptr, &flags_info);
	

	// create the scratch buffer (will be deleted at the end, not worrying about update here la
	const vk::DeviceSize scratch_size = allowUpdate ? buildScratchSize : updateScratchSize;
	vk::raii::Buffer _tmpAsBuff_Scratch{ nullptr };
	vk::raii::DeviceMemory _tmpAsBuff_Scratch_DevMem{ nullptr };
	TbUtils_Vk_Raii::vkCreate_Buffer(device, pDevice,
		_tmpAsBuff_Scratch, _tmpAsBuff_Scratch_DevMem,
		scratch_size,
		usage, memPropFlag, sharingMode,
		nullptr, &flags_info);

	// get the device address of the scratch buffer
	const vk::DeviceAddress scratchBuffAddress = TbUtils_Vk_Raii::vkQuery_DeviceAddress(device, _tmpAsBuff_Scratch);

	
	
#if USE_OLD_API_CREATE_AS
	_as_Handles.reserve(geoSize);
	for (uint32_t i = 0; i < geoSize; ++i)
	{
		auto* as_build_geo_info = &bd_geo_infos[i];
		auto* as_build_size_info = &geo_bd_sizes[i];

		// have to mix in using the old api, device
		// device.createAccelerationStructureKHR(create_info) would not work
		VkAccelerationStructureCreateInfoKHR create_info{};
		create_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR;
		create_info.type = static_cast<VkAccelerationStructureTypeKHR>(as_build_geo_info->type);
		create_info.buffer = *_as_buff;
		create_info.offset = as_build_size_info->accelerationStructureSize_offset;   // ensure buffer device address is aligned
		create_info.size = as_build_size_info->accelerationStructureSize;            // build_sizes.accelerationStructureSize;

		VkAccelerationStructureKHR ea_as;
		TBVK_ASSERT(vkCreateAccelerationStructureKHR != nullptr, "ERROR :: Func Ptr NULL : vkCreateAccelerationStructureKHR");
		vkCreateAccelerationStructureKHR(*device, &create_info, nullptr, &ea_as);
		_as_Handles.emplace_back(vk::AccelerationStructureKHR(ea_as));

		const VkDeviceSize scratch_offset = allowUpdate ?
			as_build_size_info->buildScratchSize_offset :
			as_build_size_info->updateScratchSize_offset;

		as_build_geo_info->scratchData.deviceAddress = scratchBuffAddress + scratch_offset;
		as_build_geo_info->dstAccelerationStructure = _as_Handles[i];
	}
#else
	/////////////////////////////////////////////////////////////////////////////////////////////////////
	// for some unknown reason, device.createAccelerationStructureKHR would produce invalid AS

	_as_Handles.reserve(geoSize);
	for (uint32_t i = 0; i < geoSize; ++i)
	{
		auto* as_build_geo_info = &bd_geo_infos[i];
		auto* as_build_size_info = &geo_bd_sizes[i];

		vk::AccelerationStructureCreateInfoKHR create_info{};
		create_info.type = as_build_geo_info->type;
		create_info.buffer = *_as_buff;
		create_info.offset = as_build_size_info->accelerationStructureSize_offset;   // ensure buffer device address is aligned
		create_info.size = as_build_size_info->accelerationStructureSize;            // build_sizes.accelerationStructureSize;

		// create the as_handle in place
		_as_Handles.emplace_back(device.createAccelerationStructureKHR(create_info));

		const VkDeviceSize scratch_offset = allowUpdate ?
			as_build_size_info->buildScratchSize_offset :
			as_build_size_info->updateScratchSize_offset;

		as_build_geo_info->scratchData.deviceAddress = scratchBuffAddress + scratch_offset;
		as_build_geo_info->dstAccelerationStructure = _as_Handles[i];
	}
	/////////////////////////////////////////////////////////////////////////////////////////////////////
#endif


	
	auto tmpCmd = TbUtils_Vk_Raii::vkCmd_Single_Begin(device, cmdPool);
	tmpCmd.buildAccelerationStructuresKHR(bd_geo_infos, bd_ranges_list);
	TbUtils_Vk_Raii::vkCmd_Single_End(queue, tmpCmd);

	//// raii api no need to explicit clean up
	//// clean up the scratch buffer ??? both build and update ???
	//TbUtils_Vk::vkDestroy_Buffer(device, _tmpAsBuff_Scratch, _tmpAsBuff_Scratch_DevMem);

	_buildState = 1;
	printf("....BLAS Built Completed....\n");
}

the problem was that i have to mix in the (non-raii) deviceCreateAS api, so that the validation wont complaint

when i build_tl / build_bl without “USE_OLD_API_CREATE_AS” defined (using raii deviceCreateAS api), validation error stating invalid AS, it would crash before build_xx can finish:

so i have to mixed in non-raii deviceCreateAS api
(with “USE_OLD_API_CREATE_AS” defined) , build_xx can complete without any validation err/warning

its so funny that quite often i found out the solution not long after i post in some forum for help. and before posting, i really struggled to see where the problem was.

the problem came from the following private field:

std::vector<vk::AccelerationStructureKHR>	_as_Handles;

where it should really be like :

std::vector<vk::raii::AccelerationStructureKHR>	_as_Handles;

when using raii api in my helper class.