mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-27 17:37:29 +01:00
825 lines
34 KiB
C++
825 lines
34 KiB
C++
/*
|
|
This Source Code Form is subject to the
|
|
terms of the Mozilla Public License, v.
|
|
2.0. If a copy of the MPL was not
|
|
distributed with this file, You can
|
|
obtain one at
|
|
http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "rendering/vulkan14/vulkan14raytracing.h"
|
|
#include "rendering/vulkan14/vulkan14device.h"
|
|
#include "rendering/vulkan14/vulkan14commands.h"
|
|
#include "rendering/vulkan14/vulkan14texture.h"
|
|
#include "rendering/vulkan14/vulkan14bindings.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
|
|
namespace vk14
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
// the record of an instance the shading reads (shaders/vk14/rt_reflections.glsl, rt_record)
|
|
struct record
|
|
{
|
|
VkDeviceAddress vertices;
|
|
VkDeviceAddress indices;
|
|
std::uint32_t texture;
|
|
std::uint32_t flags;
|
|
float cutoff;
|
|
float glow;
|
|
glm::vec4 colour;
|
|
};
|
|
static_assert(sizeof(record) == 48, "the record of an instance of the ray tracing doesn't match the shaders");
|
|
|
|
std::uint32_t constexpr record_alpha{1};
|
|
// a chunk of the heightmap terrain: texture holds the index of its first weights, the bits of cutoff the second weights, the bits of
|
|
// glow the table of its layers, colour the placement of the chunk (param[2] of its material)
|
|
std::uint32_t constexpr record_terrain{2};
|
|
// the bottom level structures built in a frame at most, and their triangles: the rest waits for the next frames
|
|
std::uint32_t constexpr builds_per_frame{96};
|
|
std::uint64_t constexpr triangles_per_frame{2000000};
|
|
// the instances of the top level structure at most (the records take 24 bits of the custom index)
|
|
std::uint32_t constexpr max_instances{1u << 20};
|
|
// a bottom level structure not met in this many frames goes, and a texture keeps its index in the array this long
|
|
std::uint64_t constexpr bottom_lifetime{1800};
|
|
std::uint64_t constexpr texture_lifetime{600};
|
|
// the size of the vertices of the banks (gfx::basic_vertex)
|
|
VkDeviceSize constexpr vertex_stride{48};
|
|
|
|
VkDeviceSize grow(VkDeviceSize const Needed, VkDeviceSize const Current)
|
|
{
|
|
return std::max(Needed, Current + Current / 2);
|
|
}
|
|
|
|
void destroy_structure(VkDevice const Device, VkAccelerationStructureKHR const Structure)
|
|
{
|
|
if (Structure != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyAccelerationStructureKHR(Device, Structure, nullptr);
|
|
}
|
|
}
|
|
|
|
// waits for the writes of the builds before the structures are read: by the builds which follow and by the shaders
|
|
void build_barrier(command_stream *Commands, VkPipelineStageFlags2 const Source, VkAccessFlags2 const SourceAccess, VkPipelineStageFlags2 const Destination, VkAccessFlags2 const DestinationAccess)
|
|
{
|
|
VkMemoryBarrier2 barrier{};
|
|
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
|
|
barrier.srcStageMask = Source;
|
|
barrier.srcAccessMask = SourceAccess;
|
|
barrier.dstStageMask = Destination;
|
|
barrier.dstAccessMask = DestinationAccess;
|
|
VkDependencyInfo dependency{};
|
|
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
|
dependency.memoryBarrierCount = 1;
|
|
dependency.pMemoryBarriers = &barrier;
|
|
Commands->pipeline_barrier2(&dependency);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ray_tracing::~ray_tracing()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
bool ray_tracing::init(device const &Device, allocator &Memory, deletion_queue &Deletions, std::uint32_t const Frames, std::string &Reason)
|
|
{
|
|
using namespace vk14::bindings;
|
|
m_device = &Device;
|
|
m_memory = &Memory;
|
|
m_deletions = &Deletions;
|
|
m_scratchalignment = Device.caps().scratch_alignment;
|
|
m_texturecount = std::min<std::uint32_t>(Device.caps().max_indexed_textures, 16384);
|
|
auto const handle{Device.handle()};
|
|
|
|
// the structure, the records, the layers of the terrain (written once a chunk of it is met) and the textures, the last binding as
|
|
// many as the set is made with and partly bound
|
|
static_assert((RT_TERRAIN == 2) && (RT_TEXTURES == 3), "the bindings of the set of the ray tracing go in this order");
|
|
std::array<VkDescriptorSetLayoutBinding, 4> bindings{{{RT_SCENE, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
|
|
{RT_RECORDS, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
|
|
{RT_TERRAIN, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr},
|
|
{RT_TEXTURES, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, m_texturecount, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}}};
|
|
std::array<VkDescriptorBindingFlags, 4> const flags{0, 0, VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT, VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT};
|
|
VkDescriptorSetLayoutBindingFlagsCreateInfo bindingflags{};
|
|
bindingflags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO;
|
|
bindingflags.bindingCount = static_cast<std::uint32_t>(flags.size());
|
|
bindingflags.pBindingFlags = flags.data();
|
|
VkDescriptorSetLayoutCreateInfo layout{};
|
|
layout.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
layout.pNext = &bindingflags;
|
|
layout.bindingCount = static_cast<std::uint32_t>(bindings.size());
|
|
layout.pBindings = bindings.data();
|
|
if (!VK14_VERIFY(vkCreateDescriptorSetLayout(handle, &layout, nullptr, &m_layout)))
|
|
{
|
|
m_layout = VK_NULL_HANDLE;
|
|
Reason = "no layout of the set of the ray tracing";
|
|
return false;
|
|
}
|
|
name_object(handle, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, handle_value(m_layout), "ray tracing set");
|
|
|
|
std::array<VkDescriptorPoolSize, 3> const sizes{{{VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, Frames}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, Frames}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, (m_texturecount + 1) * Frames}}};
|
|
VkDescriptorPoolCreateInfo pool{};
|
|
pool.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
|
pool.maxSets = Frames;
|
|
pool.poolSizeCount = static_cast<std::uint32_t>(sizes.size());
|
|
pool.pPoolSizes = sizes.data();
|
|
if (!VK14_VERIFY(vkCreateDescriptorPool(handle, &pool, nullptr, &m_pool)))
|
|
{
|
|
m_pool = VK_NULL_HANDLE;
|
|
Reason = "no pool of the sets of the ray tracing";
|
|
return false;
|
|
}
|
|
m_frames.resize(Frames);
|
|
std::vector<VkDescriptorSetLayout> const layouts(Frames, m_layout);
|
|
std::vector<std::uint32_t> const counts(Frames, m_texturecount);
|
|
std::vector<VkDescriptorSet> sets(Frames, VK_NULL_HANDLE);
|
|
VkDescriptorSetVariableDescriptorCountAllocateInfo variable{};
|
|
variable.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO;
|
|
variable.descriptorSetCount = Frames;
|
|
variable.pDescriptorCounts = counts.data();
|
|
VkDescriptorSetAllocateInfo allocation{};
|
|
allocation.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
|
allocation.pNext = &variable;
|
|
allocation.descriptorPool = m_pool;
|
|
allocation.descriptorSetCount = Frames;
|
|
allocation.pSetLayouts = layouts.data();
|
|
if (!VK14_VERIFY(vkAllocateDescriptorSets(handle, &allocation, sets.data())))
|
|
{
|
|
Reason = "no sets of the ray tracing";
|
|
return false;
|
|
}
|
|
for (std::uint32_t frame = 0; frame < Frames; ++frame)
|
|
{
|
|
m_frames[frame].set = sets[frame];
|
|
m_frames[frame].written_textures.assign(m_texturecount, {});
|
|
}
|
|
// the index 0 is the white texture of the records without one
|
|
m_textureowners.assign(m_texturecount, 0);
|
|
m_textureused.assign(m_texturecount, 0);
|
|
m_freetextures.clear();
|
|
for (std::uint32_t index = m_texturecount; index > 1; --index)
|
|
{
|
|
m_freetextures.push_back(index - 1);
|
|
}
|
|
log_info("ray traced reflections: up to " + std::to_string(m_texturecount) + " textures of the scenery, " + std::to_string(Frames) + " sets");
|
|
return true;
|
|
}
|
|
|
|
void ray_tracing::destroy()
|
|
{
|
|
if (m_device == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
auto const handle{m_device->handle()};
|
|
for (auto &bottom : m_bottoms)
|
|
{
|
|
destroy_structure(handle, bottom.second.handle);
|
|
}
|
|
m_bottoms.clear();
|
|
m_bottombytes = 0;
|
|
for (auto &frame : m_frames)
|
|
{
|
|
destroy_structure(handle, frame.top);
|
|
}
|
|
m_frames.clear();
|
|
if (m_pool != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyDescriptorPool(handle, m_pool, nullptr);
|
|
m_pool = VK_NULL_HANDLE;
|
|
}
|
|
if (m_layout != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyDescriptorSetLayout(handle, m_layout, nullptr);
|
|
m_layout = VK_NULL_HANDLE;
|
|
}
|
|
m_textureindices.clear();
|
|
m_device = nullptr;
|
|
}
|
|
|
|
void ray_tracing::clear(std::uint64_t const Value)
|
|
{
|
|
if (m_device == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
auto const handle{m_device->handle()};
|
|
for (auto &entry : m_bottoms)
|
|
{
|
|
m_deletions->push(Value, [handle, structure = entry.second.handle, storage = entry.second.storage, indices = entry.second.indices]() { destroy_structure(handle, structure); });
|
|
}
|
|
m_bottoms.clear();
|
|
m_bottombytes = 0;
|
|
}
|
|
|
|
bool ray_tracing::ensure(std::shared_ptr<buffer> &Buffer, VkDeviceSize const Size, VkBufferUsageFlags const Usage, memory_kind const Kind, char const *Name, std::uint64_t const Value)
|
|
{
|
|
if (Buffer && (Buffer->size() >= Size))
|
|
{
|
|
return true;
|
|
}
|
|
auto const current{Buffer ? Buffer->size() : 0};
|
|
auto made{std::make_shared<buffer>()};
|
|
if (false == made->create(*m_memory, grow(Size, current), Usage, Kind, Name))
|
|
{
|
|
return false;
|
|
}
|
|
if (Buffer)
|
|
{
|
|
m_deletions->push(Value, [old = std::move(Buffer)]() {});
|
|
}
|
|
Buffer = std::move(made);
|
|
return true;
|
|
}
|
|
|
|
VkDeviceAddress ray_tracing::aligned_scratch(frame_data const &Frame) const
|
|
{
|
|
auto const address{Frame.scratch->address()};
|
|
auto const alignment{static_cast<VkDeviceAddress>(m_scratchalignment)};
|
|
return (address + alignment - 1) / alignment * alignment;
|
|
}
|
|
|
|
void ray_tracing::begin_frame(command_stream *Commands, std::uint32_t const Slot, std::uint64_t const Value)
|
|
{
|
|
if ((m_device == nullptr) || (Slot >= m_frames.size()) || (m_frames[Slot].top != VK_NULL_HANDLE) || (m_white == VK_NULL_HANDLE))
|
|
{
|
|
return;
|
|
}
|
|
// the shaders statically use the set: it gets an empty structure, a record and the white texture before anything else
|
|
auto &frame{m_frames[Slot]};
|
|
if (false == ensure(frame.records, sizeof(record), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, memory_kind::upload, "ray tracing records", Value))
|
|
{
|
|
return;
|
|
}
|
|
std::memset(frame.records->mapped(), 0, sizeof(record));
|
|
frame.records->flush(0, sizeof(record));
|
|
if (false == build_top(Commands, frame, 0, Value))
|
|
{
|
|
return;
|
|
}
|
|
// the build of the frame later on reuses the structure and the scratch
|
|
build_barrier(Commands, VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,
|
|
VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR);
|
|
VkWriteDescriptorSetAccelerationStructureKHR structure{};
|
|
structure.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR;
|
|
structure.accelerationStructureCount = 1;
|
|
structure.pAccelerationStructures = &frame.top;
|
|
VkDescriptorBufferInfo const records{frame.records->handle(), 0, VK_WHOLE_SIZE};
|
|
VkDescriptorImageInfo const white{m_whitesampler, m_white, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
|
std::array<VkWriteDescriptorSet, 3> writes{};
|
|
for (auto &write : writes)
|
|
{
|
|
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write.dstSet = frame.set;
|
|
write.descriptorCount = 1;
|
|
}
|
|
writes[0].pNext = &structure;
|
|
writes[0].dstBinding = vk14::bindings::RT_SCENE;
|
|
writes[0].descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
|
|
writes[1].dstBinding = vk14::bindings::RT_RECORDS;
|
|
writes[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
writes[1].pBufferInfo = &records;
|
|
writes[2].dstBinding = vk14::bindings::RT_TEXTURES;
|
|
writes[2].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
writes[2].pImageInfo = &white;
|
|
vkUpdateDescriptorSets(m_device->handle(), static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
|
|
frame.written_top = frame.top;
|
|
frame.written_records = frame.records->handle();
|
|
frame.written_textures[0] = {m_white, m_whitesampler, {}};
|
|
}
|
|
|
|
bool ray_tracing::build_top(command_stream *Commands, frame_data &Frame, std::uint32_t const Count, std::uint64_t const Value)
|
|
{
|
|
auto const handle{m_device->handle()};
|
|
// the instances: room for the count, kept from frame to frame
|
|
auto const capacity{std::max<std::uint32_t>(Count, 1)};
|
|
if (false == ensure(Frame.instances, sizeof(VkAccelerationStructureInstanceKHR) * capacity, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
|
|
memory_kind::upload, "ray tracing instances", Value))
|
|
{
|
|
return false;
|
|
}
|
|
VkAccelerationStructureGeometryKHR geometry{};
|
|
geometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
|
|
geometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR;
|
|
geometry.geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR;
|
|
geometry.geometry.instances.arrayOfPointers = VK_FALSE;
|
|
geometry.geometry.instances.data.deviceAddress = Frame.instances->address();
|
|
VkAccelerationStructureBuildGeometryInfoKHR info{};
|
|
info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
|
|
info.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
|
|
info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
|
|
info.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
|
|
info.geometryCount = 1;
|
|
info.pGeometries = &geometry;
|
|
// the structure is made for more instances than the frame has, so it lasts a while as the count changes
|
|
auto count{Count};
|
|
if ((Frame.top == VK_NULL_HANDLE) || (Count > Frame.top_capacity))
|
|
{
|
|
auto const room{std::max<std::uint32_t>(static_cast<std::uint32_t>(grow(Count, Frame.top_capacity)), 64)};
|
|
VkAccelerationStructureBuildSizesInfoKHR sizes{};
|
|
sizes.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR;
|
|
vkGetAccelerationStructureBuildSizesKHR(handle, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &info, &room, &sizes);
|
|
auto storage{std::make_shared<buffer>()};
|
|
VkAccelerationStructureKHR made{VK_NULL_HANDLE};
|
|
auto ok{storage->create(*m_memory, sizes.accelerationStructureSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, memory_kind::device,
|
|
"ray tracing top level")};
|
|
if (ok)
|
|
{
|
|
VkAccelerationStructureCreateInfoKHR create{};
|
|
create.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR;
|
|
create.buffer = storage->handle();
|
|
create.size = sizes.accelerationStructureSize;
|
|
create.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
|
|
ok = VK14_VERIFY(vkCreateAccelerationStructureKHR(handle, &create, nullptr, &made));
|
|
}
|
|
if (ok)
|
|
{
|
|
name_object(handle, VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR, handle_value(made), "ray tracing top level");
|
|
if (Frame.top != VK_NULL_HANDLE)
|
|
{
|
|
m_deletions->push(Value, [handle, structure = Frame.top, oldstorage = Frame.storage]() { destroy_structure(handle, structure); });
|
|
}
|
|
Frame.top = made;
|
|
Frame.storage = std::move(storage);
|
|
Frame.top_capacity = room;
|
|
Frame.top_scratch = sizes.buildScratchSize + m_scratchalignment;
|
|
}
|
|
else
|
|
{
|
|
// no memory for a bigger one: the old one (the set holds it) with as many instances as it has room for
|
|
if (Frame.top == VK_NULL_HANDLE)
|
|
{
|
|
return false;
|
|
}
|
|
count = Frame.top_capacity;
|
|
}
|
|
}
|
|
if (false == ensure(Frame.scratch, Frame.top_scratch, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, memory_kind::device, "ray tracing scratch", Value))
|
|
{
|
|
return false;
|
|
}
|
|
info.dstAccelerationStructure = Frame.top;
|
|
info.scratchData.deviceAddress = aligned_scratch(Frame);
|
|
VkAccelerationStructureBuildRangeInfoKHR range{};
|
|
range.primitiveCount = count;
|
|
VkAccelerationStructureBuildRangeInfoKHR const *ranges{&range};
|
|
Commands->build_acceleration_structures(1, &info, &ranges);
|
|
return true;
|
|
}
|
|
|
|
std::uint32_t ray_tracing::texture_index(std::uint64_t const Id, std::uint64_t const Frame)
|
|
{
|
|
if (Id == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
auto const lookup{m_textureindices.find(Id)};
|
|
if (lookup != m_textureindices.end())
|
|
{
|
|
m_textureused[lookup->second] = Frame;
|
|
return lookup->second;
|
|
}
|
|
if (m_freetextures.empty())
|
|
{
|
|
// the indices not used for a while are taken back (the sets write them anew before they're read again)
|
|
for (std::uint32_t index = 1; index < m_texturecount; ++index)
|
|
{
|
|
if ((m_textureowners[index] != 0) && (m_textureused[index] + texture_lifetime < Frame))
|
|
{
|
|
m_textureindices.erase(m_textureowners[index]);
|
|
m_textureowners[index] = 0;
|
|
m_freetextures.push_back(index);
|
|
}
|
|
}
|
|
if (m_freetextures.empty())
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
auto const index{m_freetextures.back()};
|
|
m_freetextures.pop_back();
|
|
m_textureindices.emplace(Id, index);
|
|
m_textureowners[index] = Id;
|
|
m_textureused[index] = Frame;
|
|
return index;
|
|
}
|
|
|
|
void ray_tracing::build(command_stream *Commands, std::uint32_t const Slot, std::uint64_t const Value, std::uint64_t const Frame, std::vector<ray_draw> const &Draws, image_table const &Images)
|
|
{
|
|
if ((m_device == nullptr) || (Slot >= m_frames.size()))
|
|
{
|
|
return;
|
|
}
|
|
auto const handle{m_device->handle()};
|
|
auto &frame{m_frames[Slot]};
|
|
|
|
// the bottom level structures the draws need: the ones not there yet or out of date are built, as many as a frame lets
|
|
struct pending_build
|
|
{
|
|
VkAccelerationStructureGeometryKHR geometry;
|
|
VkAccelerationStructureBuildRangeInfoKHR range;
|
|
VkAccelerationStructureBuildGeometryInfoKHR info;
|
|
VkDeviceSize scratch;
|
|
};
|
|
std::vector<pending_build> builds;
|
|
std::uint64_t triangles{0};
|
|
std::uint32_t waiting{0};
|
|
VkDeviceSize scratchsize{0};
|
|
auto const scratchalignment{static_cast<VkDeviceSize>(m_scratchalignment)};
|
|
for (auto const &draw : Draws)
|
|
{
|
|
auto lookup{m_bottoms.find(draw.key)};
|
|
if ((lookup != m_bottoms.end()) && (lookup->second.generation == draw.source.generation))
|
|
{
|
|
lookup->second.used = Frame;
|
|
continue;
|
|
}
|
|
if ((builds.size() >= builds_per_frame) || (triangles >= triangles_per_frame))
|
|
{
|
|
++waiting;
|
|
continue;
|
|
}
|
|
auto const &source{draw.source};
|
|
auto const indexed{source.indices != VK_NULL_HANDLE};
|
|
// the strips and the fans (the tracks and the roads are strips) get triangles of their own, as indices kept with the structure
|
|
auto const strip{source.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP};
|
|
auto const fan{source.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN};
|
|
if ((strip || fan) && indexed)
|
|
{
|
|
continue;
|
|
}
|
|
auto const primitives{(strip || fan) ? (source.count >= 3 ? source.count - 2 : 0) : source.count / 3};
|
|
if ((primitives == 0) || (source.vertex_address == 0) || (indexed && (source.index_address == 0)) || (source.vertexcount == 0))
|
|
{
|
|
continue;
|
|
}
|
|
std::shared_ptr<buffer> generated;
|
|
if (strip || fan)
|
|
{
|
|
auto const bytes{sizeof(std::uint32_t) * 3 * static_cast<VkDeviceSize>(primitives)};
|
|
generated = std::make_shared<buffer>();
|
|
if ((false == generated->create(*m_memory, bytes, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, memory_kind::upload, "ray tracing strip indices")) ||
|
|
(generated->mapped() == nullptr))
|
|
{
|
|
continue;
|
|
}
|
|
auto *indices{reinterpret_cast<std::uint32_t *>(generated->mapped())};
|
|
for (std::uint32_t triangle = 0; triangle < primitives; ++triangle)
|
|
{
|
|
indices[triangle * 3 + 0] = (strip ? triangle : 0);
|
|
indices[triangle * 3 + 1] = triangle + 1;
|
|
indices[triangle * 3 + 2] = triangle + 2;
|
|
}
|
|
generated->flush(0, bytes);
|
|
}
|
|
pending_build build{};
|
|
build.geometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
|
|
build.geometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
|
|
auto &triangles_data{build.geometry.geometry.triangles};
|
|
triangles_data.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
|
|
triangles_data.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT;
|
|
triangles_data.vertexData.deviceAddress = source.vertex_address + vertex_stride * source.vertexfirst;
|
|
triangles_data.vertexStride = vertex_stride;
|
|
triangles_data.maxVertex = source.vertexcount - 1;
|
|
triangles_data.indexType = ((indexed || generated) ? VK_INDEX_TYPE_UINT32 : VK_INDEX_TYPE_NONE_KHR);
|
|
triangles_data.indexData.deviceAddress = (generated ? generated->address() : indexed ? source.index_address + sizeof(std::uint32_t) * source.first : 0);
|
|
build.range.primitiveCount = primitives;
|
|
build.info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
|
|
build.info.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
|
|
build.info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
|
|
build.info.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
|
|
build.info.geometryCount = 1;
|
|
VkAccelerationStructureBuildSizesInfoKHR sizes{};
|
|
sizes.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR;
|
|
build.info.pGeometries = &build.geometry;
|
|
vkGetAccelerationStructureBuildSizesKHR(handle, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &build.info, &primitives, &sizes);
|
|
build.info.pGeometries = nullptr;
|
|
auto storage{std::make_shared<buffer>()};
|
|
if (false == storage->create(*m_memory, sizes.accelerationStructureSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, memory_kind::device, "ray tracing bottom level"))
|
|
{
|
|
continue;
|
|
}
|
|
VkAccelerationStructureCreateInfoKHR create{};
|
|
create.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR;
|
|
create.buffer = storage->handle();
|
|
create.size = sizes.accelerationStructureSize;
|
|
create.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
|
|
VkAccelerationStructureKHR structure{VK_NULL_HANDLE};
|
|
if (!VK14_VERIFY(vkCreateAccelerationStructureKHR(handle, &create, nullptr, &structure)))
|
|
{
|
|
continue;
|
|
}
|
|
if (lookup != m_bottoms.end())
|
|
{
|
|
// the data of the chunk changed: the old structure goes once the frames which read it are done
|
|
m_bottombytes -= lookup->second.bytes;
|
|
m_deletions->push(Value, [handle, old = lookup->second.handle, oldstorage = lookup->second.storage, oldindices = lookup->second.indices]() { destroy_structure(handle, old); });
|
|
}
|
|
VkAccelerationStructureDeviceAddressInfoKHR addressinfo{};
|
|
addressinfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR;
|
|
addressinfo.accelerationStructure = structure;
|
|
auto &entry{m_bottoms[draw.key]};
|
|
entry.storage = std::move(storage);
|
|
entry.handle = structure;
|
|
entry.address = vkGetAccelerationStructureDeviceAddressKHR(handle, &addressinfo);
|
|
entry.generation = source.generation;
|
|
entry.used = Frame;
|
|
entry.bytes = sizes.accelerationStructureSize + (generated ? generated->size() : 0);
|
|
entry.index_address = (generated ? generated->address() : 0);
|
|
entry.indices = std::move(generated);
|
|
m_bottombytes += entry.bytes;
|
|
build.info.dstAccelerationStructure = structure;
|
|
build.scratch = scratchsize;
|
|
scratchsize += (sizes.buildScratchSize + scratchalignment - 1) / scratchalignment * scratchalignment;
|
|
builds.push_back(build);
|
|
triangles += primitives;
|
|
}
|
|
|
|
// the vertices and the indices copied to the banks in this frame (the uploads go to the queue before the frame) are read by the builds
|
|
if (false == builds.empty())
|
|
{
|
|
build_barrier(Commands, VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_ACCESS_2_SHADER_READ_BIT);
|
|
if (false == ensure(frame.scratch, scratchsize + scratchalignment, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, memory_kind::device, "ray tracing scratch", Value))
|
|
{
|
|
// no memory for the builds: the structures made for them go, they're tried again
|
|
for (auto const &build : builds)
|
|
{
|
|
for (auto it = m_bottoms.begin(); it != m_bottoms.end(); ++it)
|
|
{
|
|
if (it->second.handle == build.info.dstAccelerationStructure)
|
|
{
|
|
m_bottombytes -= it->second.bytes;
|
|
m_deletions->push(Value, [handle, old = it->second.handle, oldstorage = it->second.storage, oldindices = it->second.indices]() { destroy_structure(handle, old); });
|
|
m_bottoms.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
builds.clear();
|
|
}
|
|
else
|
|
{
|
|
auto const scratchbase{aligned_scratch(frame)};
|
|
std::vector<VkAccelerationStructureBuildGeometryInfoKHR> infos;
|
|
std::vector<VkAccelerationStructureBuildRangeInfoKHR const *> ranges;
|
|
infos.reserve(builds.size());
|
|
ranges.reserve(builds.size());
|
|
for (auto &build : builds)
|
|
{
|
|
build.info.pGeometries = &build.geometry;
|
|
build.info.scratchData.deviceAddress = scratchbase + build.scratch;
|
|
infos.push_back(build.info);
|
|
ranges.push_back(&build.range);
|
|
}
|
|
Commands->build_acceleration_structures(static_cast<std::uint32_t>(infos.size()), infos.data(), ranges.data());
|
|
// the top level structure reads them, and its build uses the scratch memory again
|
|
build_barrier(Commands, VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
|
VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR);
|
|
}
|
|
}
|
|
|
|
// the records and the instances of the draws with their structures
|
|
std::size_t instancecount{0};
|
|
for (auto const &draw : Draws)
|
|
{
|
|
auto const lookup{m_bottoms.find(draw.key)};
|
|
if ((lookup != m_bottoms.end()) && (lookup->second.generation == draw.source.generation))
|
|
{
|
|
++instancecount;
|
|
}
|
|
}
|
|
instancecount = std::min<std::size_t>(instancecount, max_instances);
|
|
if ((false == ensure(frame.records, sizeof(record) * std::max<std::size_t>(instancecount, 1), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, memory_kind::upload, "ray tracing records", Value)) ||
|
|
(false == ensure(frame.instances, sizeof(VkAccelerationStructureInstanceKHR) * std::max<std::size_t>(instancecount, 1),
|
|
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, memory_kind::upload, "ray tracing instances", Value)))
|
|
{
|
|
instancecount = 0;
|
|
}
|
|
auto *records{reinterpret_cast<record *>(frame.records ? frame.records->mapped() : nullptr)};
|
|
auto *instances{reinterpret_cast<VkAccelerationStructureInstanceKHR *>(frame.instances ? frame.instances->mapped() : nullptr)};
|
|
std::vector<std::uint32_t> usedtextures;
|
|
// the texture array of the layers of the terrain the chunks read: a 2d array of the image table, or the chunks shade with their
|
|
// colour (the binding is read only for the chunks marked as terrain)
|
|
std::uint64_t terrainlayers{0};
|
|
std::uint64_t checkedlayers{0};
|
|
image_table::entry const *layersentry{nullptr};
|
|
auto const layers_valid = [&](std::uint64_t const Id) {
|
|
if (Id != checkedlayers)
|
|
{
|
|
checkedlayers = Id;
|
|
layersentry = Images.find(Id);
|
|
if ((layersentry != nullptr) && ((false == bool(layersentry->target)) || (layersentry->target->desc().view_type != VK_IMAGE_VIEW_TYPE_2D_ARRAY) || (layersentry->sampler == VK_NULL_HANDLE)))
|
|
{
|
|
layersentry = nullptr;
|
|
}
|
|
}
|
|
return (layersentry != nullptr) && ((terrainlayers == 0) || (terrainlayers == Id));
|
|
};
|
|
std::uint32_t written{0};
|
|
for (auto const &draw : Draws)
|
|
{
|
|
if ((written >= instancecount) || (records == nullptr) || (instances == nullptr))
|
|
{
|
|
break;
|
|
}
|
|
auto const lookup{m_bottoms.find(draw.key)};
|
|
if ((lookup == m_bottoms.end()) || (lookup->second.generation != draw.source.generation))
|
|
{
|
|
continue;
|
|
}
|
|
auto const &source{draw.source};
|
|
auto const terrain{draw.terrain_table != 0};
|
|
auto const texture{texture_index(terrain ? draw.terrain_weights[0] : draw.texture, Frame)};
|
|
if (texture != 0)
|
|
{
|
|
usedtextures.push_back(texture);
|
|
}
|
|
auto &entry{records[written]};
|
|
entry.vertices = source.vertex_address + vertex_stride * source.vertexfirst;
|
|
entry.indices = (lookup->second.index_address != 0 ? lookup->second.index_address : source.indices != VK_NULL_HANDLE ? source.index_address + sizeof(std::uint32_t) * source.first : 0);
|
|
entry.texture = texture;
|
|
auto const alpha{(false == terrain) && (texture != 0) && (draw.cutoff > 0.f)};
|
|
entry.flags = (alpha ? record_alpha : 0);
|
|
entry.cutoff = draw.cutoff;
|
|
entry.glow = draw.glow;
|
|
entry.colour = glm::vec4{draw.colour, 1.f};
|
|
if (terrain)
|
|
{
|
|
// the shading blends the layers as the terrain shader does, from the weights and the table (indices in the bits of the floats)
|
|
auto const weights{texture_index(draw.terrain_weights[1], Frame)};
|
|
auto const table{texture_index(draw.terrain_table, Frame)};
|
|
for (auto const index : {weights, table})
|
|
{
|
|
if (index != 0)
|
|
{
|
|
usedtextures.push_back(index);
|
|
}
|
|
}
|
|
if ((texture != 0) && (weights != 0) && (table != 0) && (draw.terrain_layers != 0) && layers_valid(draw.terrain_layers))
|
|
{
|
|
entry.flags = record_terrain;
|
|
std::memcpy(&entry.cutoff, &weights, sizeof(weights));
|
|
std::memcpy(&entry.glow, &table, sizeof(table));
|
|
entry.colour = draw.placement;
|
|
terrainlayers = draw.terrain_layers;
|
|
}
|
|
else
|
|
{
|
|
entry.texture = 0;
|
|
}
|
|
}
|
|
auto &instance{instances[written]};
|
|
// the rows of the transform, which glm keeps by the columns
|
|
for (int row = 0; row < 3; ++row)
|
|
{
|
|
for (int column = 0; column < 4; ++column)
|
|
{
|
|
instance.transform.matrix[row][column] = draw.transform[column][row];
|
|
}
|
|
}
|
|
instance.instanceCustomIndex = written;
|
|
// the shaders tell the alpha tested ones by the mask (shaders/vk14/rt_reflections.glsl, RT_MASK_*)
|
|
instance.mask = (alpha ? 0x02 : 0x01);
|
|
instance.instanceShaderBindingTableRecordOffset = 0;
|
|
// the models have faces of one side, seen from both in the reflections; an alpha tested texture leaves holes in its faces
|
|
instance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR | (alpha ? VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR : VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR);
|
|
instance.accelerationStructureReference = lookup->second.address;
|
|
++written;
|
|
}
|
|
if (written > 0)
|
|
{
|
|
frame.records->flush(0, sizeof(record) * written);
|
|
frame.instances->flush(0, sizeof(VkAccelerationStructureInstanceKHR) * written);
|
|
}
|
|
if (build_top(Commands, frame, written, Value))
|
|
{
|
|
build_barrier(Commands, VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
|
|
VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR);
|
|
}
|
|
|
|
// the set of the frame: what changed in it
|
|
std::vector<VkWriteDescriptorSet> writes;
|
|
std::vector<VkDescriptorImageInfo> images;
|
|
images.reserve(usedtextures.size());
|
|
VkWriteDescriptorSetAccelerationStructureKHR structure{};
|
|
structure.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR;
|
|
structure.accelerationStructureCount = 1;
|
|
structure.pAccelerationStructures = &frame.top;
|
|
VkDescriptorBufferInfo const recordsinfo{frame.records ? frame.records->handle() : VK_NULL_HANDLE, 0, VK_WHOLE_SIZE};
|
|
if ((frame.top != VK_NULL_HANDLE) && (frame.top != frame.written_top))
|
|
{
|
|
VkWriteDescriptorSet write{};
|
|
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write.pNext = &structure;
|
|
write.dstSet = frame.set;
|
|
write.dstBinding = vk14::bindings::RT_SCENE;
|
|
write.descriptorCount = 1;
|
|
write.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
|
|
writes.push_back(write);
|
|
frame.written_top = frame.top;
|
|
}
|
|
if (frame.records && (frame.records->handle() != frame.written_records))
|
|
{
|
|
VkWriteDescriptorSet write{};
|
|
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write.dstSet = frame.set;
|
|
write.dstBinding = vk14::bindings::RT_RECORDS;
|
|
write.descriptorCount = 1;
|
|
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
write.pBufferInfo = &recordsinfo;
|
|
writes.push_back(write);
|
|
frame.written_records = frame.records->handle();
|
|
}
|
|
std::sort(usedtextures.begin(), usedtextures.end());
|
|
usedtextures.erase(std::unique(usedtextures.begin(), usedtextures.end()), usedtextures.end());
|
|
for (auto const index : usedtextures)
|
|
{
|
|
// the texture as its material samples it; a texture which isn't a plain 2d image (or is gone) reads as white
|
|
auto view{m_white};
|
|
auto sampler{m_whitesampler};
|
|
std::shared_ptr<image> source;
|
|
if (auto const *entry{Images.find(m_textureowners[index])})
|
|
{
|
|
if (entry->target && (entry->target->desc().view_type == VK_IMAGE_VIEW_TYPE_2D) && (entry->sampler != VK_NULL_HANDLE))
|
|
{
|
|
view = entry->target->view();
|
|
sampler = entry->sampler;
|
|
source = entry->target;
|
|
}
|
|
}
|
|
auto &cached{frame.written_textures[index]};
|
|
if ((cached.view == view) && (cached.sampler == sampler) && (cached.source.lock() == source))
|
|
{
|
|
continue;
|
|
}
|
|
cached = {view, sampler, source};
|
|
images.push_back({sampler, view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL});
|
|
VkWriteDescriptorSet write{};
|
|
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write.dstSet = frame.set;
|
|
write.dstBinding = vk14::bindings::RT_TEXTURES;
|
|
write.dstArrayElement = index;
|
|
write.descriptorCount = 1;
|
|
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
write.pImageInfo = &images.back();
|
|
writes.push_back(write);
|
|
}
|
|
VkDescriptorImageInfo terraininfo{};
|
|
if ((terrainlayers != 0) && layers_valid(terrainlayers))
|
|
{
|
|
auto const &target{layersentry->target};
|
|
auto &cached{frame.written_terrain};
|
|
if ((cached.view != target->view()) || (cached.sampler != layersentry->sampler) || (cached.source.lock() != target))
|
|
{
|
|
cached = {target->view(), layersentry->sampler, target};
|
|
terraininfo = {layersentry->sampler, target->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
|
VkWriteDescriptorSet write{};
|
|
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write.dstSet = frame.set;
|
|
write.dstBinding = vk14::bindings::RT_TERRAIN;
|
|
write.descriptorCount = 1;
|
|
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
write.pImageInfo = &terraininfo;
|
|
writes.push_back(write);
|
|
}
|
|
}
|
|
if (false == writes.empty())
|
|
{
|
|
vkUpdateDescriptorSets(handle, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
|
|
}
|
|
|
|
// the structures not met for a while go
|
|
for (auto it = m_bottoms.begin(); it != m_bottoms.end();)
|
|
{
|
|
if (it->second.used + bottom_lifetime < Frame)
|
|
{
|
|
m_bottombytes -= it->second.bytes;
|
|
m_deletions->push(Value, [handle, old = it->second.handle, oldstorage = it->second.storage, oldindices = it->second.indices]() { destroy_structure(handle, old); });
|
|
it = m_bottoms.erase(it);
|
|
}
|
|
else
|
|
{
|
|
++it;
|
|
}
|
|
}
|
|
|
|
m_lastinstances = written;
|
|
m_lastrecords = written;
|
|
m_lastbuilt = static_cast<std::uint32_t>(builds.size());
|
|
m_lastwaiting = waiting;
|
|
m_lasttextures = static_cast<std::uint32_t>(usedtextures.size());
|
|
}
|
|
|
|
std::string ray_tracing::info() const
|
|
{
|
|
return "ray traced reflections: " + std::to_string(m_lastinstances) + " instances, " + std::to_string(m_bottoms.size()) + " chunks (" + memory_size_string(m_bottombytes) + "), built " + std::to_string(m_lastbuilt) +
|
|
", waiting " + std::to_string(m_lastwaiting) + ", textures " + std::to_string(m_lasttextures);
|
|
}
|
|
|
|
} // namespace vk14
|