/* 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 #include 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(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 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 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(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(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 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(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 const layouts(Frames, m_layout); std::vector const counts(Frames, m_texturecount); std::vector 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, 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()}; 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(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 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(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(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(static_cast(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()}; 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 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 builds; std::uint64_t triangles{0}; std::uint32_t waiting{0}; VkDeviceSize scratchsize{0}; auto const scratchalignment{static_cast(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 generated; if (strip || fan) { auto const bytes{sizeof(std::uint32_t) * 3 * static_cast(primitives)}; generated = std::make_shared(); 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(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()}; 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 infos; std::vector 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(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(instancecount, max_instances); if ((false == ensure(frame.records, sizeof(record) * std::max(instancecount, 1), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, memory_kind::upload, "ray tracing records", Value)) || (false == ensure(frame.instances, sizeof(VkAccelerationStructureInstanceKHR) * std::max(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(frame.records ? frame.records->mapped() : nullptr)}; auto *instances{reinterpret_cast(frame.instances ? frame.instances->mapped() : nullptr)}; std::vector 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 writes; std::vector 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 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(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(builds.size()); m_lastwaiting = waiting; m_lasttextures = static_cast(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