Vulkan Ray Tracing (VK_KHR_ray_tracing) - построение BLAS
Пытаюсь разобраться с трассировкой лучей в Vulkan. Для того чтобы трассировать сцену в Vulkan используются структуры ускорения (acceleration structures), благодаря которым лучу не приходится перебирать все треугольники сцены. Поэтому, прежде всего нужно создать эти самые структуры ускорения. Есть 2 типа - BLAS (нижнего уровня - содержат геометрию) и TLAS (верхнего уровня - содержат инстансы и матрицы трансформаций). Я сейчас пытаюсь создать BLAS (структуру нижнего уровня)
У меня есть обычный рендерер на Vulkan, в котором есть такое понятие как "геометрический буфер". Это класс, который содержит буфер вершин и буфер индексов. Сама по себе вершина - это структура из набора атрибутов вершины (положение, нормаль, цвет и т.д). Я подумал что для того чтобы создать BLAS можно как раз использовать в качестве буфера геометрии для BLAS тот же самый буфер вершин, что использовался при обычной растеризации (тем более что при описании геометрии указывается stride)
Но меня смутил один момент, что мы не описываем отдельные атрибуты вершины. Просто указывает формат вершины.
accelerationStructureCreateGeometryTypeInfoKhr.vertexFormat = vk::Format::eR32G32B32Sfloat;
Будто бы в вершине ничего кроме положения нет и при трассировке никакого доступа к атрибутам не будет. Ну да ладно, даже если так, все равно, при выделении, чуть позже, указывается stride (шаг), так что в теории всё должно сработать..
asStructureGeometryKhr.geometry.triangles.vertexStride = static_cast<uint32_t>(sizeof(vk::tools::Vertex));
Построение BLAS происходит на стороне устройства, поэтому нужно использовать команды. Сейчас я приведу листинг моей функции построения BLAS (будет много кода, но в целом он простой)
void buildBottomLevelAccelerationStructure(const vk::BuildAccelerationStructureFlagsKHR& buildFlags = vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace)
{
// Общая информация о геометрии используемой в BLAS
vk::AccelerationStructureCreateGeometryTypeInfoKHR accelerationStructureCreateGeometryTypeInfoKhr = {};
accelerationStructureCreateGeometryTypeInfoKhr.geometryType = vk::GeometryTypeKHR::eTriangles;
accelerationStructureCreateGeometryTypeInfoKhr.vertexFormat = vk::Format::eR32G32B32Sfloat;
accelerationStructureCreateGeometryTypeInfoKhr.indexType = vk::IndexType::eUint32;
accelerationStructureCreateGeometryTypeInfoKhr.maxVertexCount = static_cast<uint32_t>(vertexCount_);
accelerationStructureCreateGeometryTypeInfoKhr.maxPrimitiveCount = (isIndexed_ ? static_cast<uint32_t>(indexCount_ / 3) : static_cast<uint32_t>(vertexCount_ / 3));
accelerationStructureCreateGeometryTypeInfoKhr.allowsTransforms = VK_FALSE;
// Создать идентификатор структуры ускорения и выделить память для последующего построения
vk::AccelerationStructureCreateInfoKHR accelerationStructureCreateInfoKhr = {};
accelerationStructureCreateInfoKhr.type = vk::AccelerationStructureTypeKHR::eBottomLevel;
accelerationStructureCreateInfoKhr.flags = buildFlags;
accelerationStructureCreateInfoKhr.maxGeometryCount = 1;
accelerationStructureCreateInfoKhr.pGeometryInfos = &accelerationStructureCreateGeometryTypeInfoKhr;
accelerationStructureKhr_ = pDevice_->getLogicalDevice()->createAccelerationStructureKHRUnique(accelerationStructureCreateInfoKhr);
// Определить требования к памяти для создания рабочего буфера (используемого для ПОСТРОЕНИЯ структуры BLAS)
auto memReqScratch = pDevice_->getLogicalDevice()->getAccelerationStructureMemoryRequirementsKHR(vk::AccelerationStructureMemoryRequirementsInfoKHR(
vk::AccelerationStructureMemoryRequirementsTypeKHR::eBuildScratch,
vk::AccelerationStructureBuildTypeKHR::eDevice,
accelerationStructureKhr_.get()
));
// Определить требования к памяти самой структуры BLAS
auto memReqObject = pDevice_->getLogicalDevice()->getAccelerationStructureMemoryRequirementsKHR(vk::AccelerationStructureMemoryRequirementsInfoKHR(
vk::AccelerationStructureMemoryRequirementsTypeKHR::eObject,
vk::AccelerationStructureBuildTypeKHR::eDevice,
accelerationStructureKhr_.get()
));
// Память для BLAS
const auto memoryTypeIndex = pDevice_->getMemoryTypeIndex(memReqObject.memoryRequirements.memoryTypeBits,vk::MemoryPropertyFlagBits::eDeviceLocal);
if(memoryTypeIndex == -1) throw vk::FeatureNotPresentError("Can't use required memory type for BLAS allocation");
vk::MemoryAllocateInfo memoryAllocateInfo;
memoryAllocateInfo.allocationSize = memReqObject.memoryRequirements.size;
memoryAllocateInfo.memoryTypeIndex = static_cast<uint32_t>(memoryTypeIndex);
accelerationStructureMemory_ = pDevice_->getLogicalDevice()->allocateMemoryUnique(memoryAllocateInfo);
// Связать память с идентификатором BLAS
pDevice_->getLogicalDevice()->bindAccelerationStructureMemoryKHR(vk::BindAccelerationStructureMemoryInfoKHR(
accelerationStructureKhr_.get(),
accelerationStructureMemory_.get(),
0,
0,
nullptr
));
// Создать рабочий буфер (для построения BLAS)
vk::tools::Buffer scratchBuffer = vk::tools::Buffer(pDevice_,
memReqScratch.memoryRequirements.size,
vk::BufferUsageFlagBits::eRayTracingKHR | vk::BufferUsageFlagBits::eShaderDeviceAddress,
vk::MemoryPropertyFlagBits::eDeviceLocal);
// Подробная информация о геометрии используемой для построения BLAS (связь с реальными геометрическими буферами)
vk::AccelerationStructureGeometryKHR asStructureGeometryKhr = {};
asStructureGeometryKhr.geometryType = vk::GeometryTypeKHR::eTriangles;
asStructureGeometryKhr.geometry.triangles.vertexData.deviceAddress = pDevice_->getLogicalDevice()->getBufferAddress(vertexBuffer_.getBuffer().get());
asStructureGeometryKhr.geometry.triangles.indexData.deviceAddress = (isIndexed_ ? pDevice_->getLogicalDevice()->getBufferAddress(indexBuffer_.getBuffer().get()) : VK_NULL_HANDLE);
asStructureGeometryKhr.geometry.triangles.vertexStride = static_cast<uint32_t>(sizeof(vk::tools::Vertex));
asStructureGeometryKhr.geometry.triangles.vertexFormat = vk::Format::eR32G32B32Sfloat;
asStructureGeometryKhr.geometry.triangles.indexType = vk::IndexType::eUint32;
asStructureGeometryKhr.geometry.triangles.transformData = {};
asStructureGeometryKhr.flags = vk::GeometryFlagBitsKHR::eNoDuplicateAnyHitInvocation;
// Информация о смещениях в буфере геометрии
vk::AccelerationStructureBuildOffsetInfoKHR accelerationStructureBuildOffsetInfoKhr = {};
accelerationStructureBuildOffsetInfoKhr.firstVertex = 0;
accelerationStructureBuildOffsetInfoKhr.primitiveCount = accelerationStructureCreateGeometryTypeInfoKhr.maxPrimitiveCount;
accelerationStructureBuildOffsetInfoKhr.primitiveOffset = 0;
accelerationStructureBuildOffsetInfoKhr.transformOffset = 0;
// Информация для команды построения буфера
vk::AccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfoKhr = {};
accelerationStructureBuildGeometryInfoKhr.flags = buildFlags;
accelerationStructureBuildGeometryInfoKhr.update = VK_FALSE;
accelerationStructureBuildGeometryInfoKhr.srcAccelerationStructure = nullptr;
accelerationStructureBuildGeometryInfoKhr.dstAccelerationStructure = accelerationStructureKhr_.get();
accelerationStructureBuildGeometryInfoKhr.geometryArrayOfPointers = VK_FALSE;
accelerationStructureBuildGeometryInfoKhr.geometryCount = 1;
const vk::AccelerationStructureGeometryKHR* pGeometry = &asStructureGeometryKhr;
accelerationStructureBuildGeometryInfoKhr.ppGeometries = &pGeometry;
accelerationStructureBuildGeometryInfoKhr.scratchData.deviceAddress = pDevice_->getLogicalDevice()->getBufferAddress(scratchBuffer.getBuffer().get());
// Выделить командный буфер для исполнения команды построения BLAS
vk::CommandBufferAllocateInfo commandBufferAllocateInfo = {};
commandBufferAllocateInfo.commandBufferCount = 1;
commandBufferAllocateInfo.commandPool = pDevice_->getCommandGfxPool().get();
commandBufferAllocateInfo.level = vk::CommandBufferLevel::ePrimary;
auto cmdBuffers = pDevice_->getLogicalDevice()->allocateCommandBuffers(commandBufferAllocateInfo);
// Записать команду в буфер
cmdBuffers[0].begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
cmdBuffers[0].buildAccelerationStructureKHR(accelerationStructureBuildGeometryInfoKhr,&accelerationStructureBuildOffsetInfoKhr);
cmdBuffers[0].end();
// Отправить команду в очередь и подождать выполнения
vk::SubmitInfo submitInfo = {};
submitInfo.commandBufferCount = cmdBuffers.size();
submitInfo.pCommandBuffers = cmdBuffers.data();
pDevice_->getGraphicsQueue().submit({submitInfo},{});
pDevice_->getGraphicsQueue().waitIdle();
// Очищаем буфер команд
pDevice_->getLogicalDevice()->free(pDevice_->getCommandGfxPool().get(),cmdBuffers.size(),cmdBuffers.data());
// Очищаем рабочий буфер
scratchBuffer.destroyVulkanResources();
}
И в итоге BLAS таки строится, но строится он по каким-то причинам только если кол-во примитивов не более двух. То есть, если в геометрическом буфере у меня 2 треугольника - всё работает (по крайней мере не вызывает ошибок). Если же индексов (и примитивов соответственно) больше - при построении возникает ошибка "Device Lost" во время исполнения команды. Я уже второй день с этим мучаюсь и понять не могу в чём дело. И самое обидное что с beta-драйвером нельзя почему-то использовать слои валидации (они не поддерживаются), и по какой причине возникает эта ошибка - остается только гадать.
Почти во всех туториалах буфер вершин, который используется для построения BLAS, это обычный массив vec3. У меня же это набор структур Vertex, которая из себя представляет следующее:
/**
* Вершина
*/
struct Vertex
{
// Базовые атрибуты
glm::vec3 position;
glm::vec3 color;
glm::vec2 uv;
glm::vec3 normal;
// Данные для скелета
glm::ivec4 boneIndices = {0,0,0,0};
glm::vec4 weights = {1.0f,0,0,0};
};
В вершине у меня содержится не только положение, и конечно же такая вершина никак не соответствует формату vk::Format::eR32G32B32Sfloat, но поскольку у меня положение (position) идет в самом начале, и оно как раз соответствует этому формату, а при построении задается stride (равный размеру структуры) - проблем ведь быть не должно?
В чем может быть дело? Что я упускаю? Что я делаю не так?