Files
2026-11-09 20:59:34 +05:00

1340 lines
77 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
#include <vulkan/vulkan.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <random>
#include <vector>
#include "video_core/host_shaders/contact_shadow_comp.h"
#include "video_core/host_shaders/rt_shadow_comp.h"
#include "video_core/host_shaders/rt_shadow_filter_comp.h"
#include "video_core/host_shaders/rt_lab_comp.h"
#include "video_core/host_shaders/point_shadow_ray_comp.h"
#include "video_core/host_shaders/rt_unproject_comp.h"
#include "video_core/host_shaders/ssgi_gather_ray_comp.h"
namespace {
#define CHECK(call) do { if (VkResult r_ = (call); r_ != VK_SUCCESS) { std::fprintf(stderr, "%s failed: %d\n", #call, int(r_)); std::exit(1); } } while (false)
constexpr unsigned W = 1920, H = 1080, Atlas = 4096;
constexpr VkDeviceSize AsAlign = 256;
struct Vec3 { float x, y, z; };
Vec3 operator+(Vec3 a, Vec3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
Vec3 operator*(Vec3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
float Dot(Vec3 a, Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
Vec3 Cross(Vec3 a, Vec3 b) { return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; }
Vec3 Norm(Vec3 a) { return a * (1.0f / std::sqrt(Dot(a, a))); }
struct Mat4 {
std::array<float, 16> m{};
float& at(int r, int c) { return m[r * 4 + c]; }
float at(int r, int c) const { return m[r * 4 + c]; }
};
Mat4 Identity() { Mat4 r; for (int i = 0; i < 4; ++i) r.at(i, i) = 1; return r; }
Mat4 operator*(const Mat4& a, const Mat4& b) {
Mat4 r;
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 4; ++j)
for (int k = 0; k < 4; ++k) r.at(i, j) += a.at(i, k) * b.at(k, j);
return r;
}
Mat4 Inverse(const Mat4& a) {
std::array<double, 32> t{};
for (int r = 0; r < 4; ++r) {
for (int c = 0; c < 4; ++c) t[r * 8 + c] = a.at(r, c);
t[r * 8 + 4 + r] = 1;
}
for (int p = 0; p < 4; ++p) {
int best = p;
for (int r = p; r < 4; ++r) if (std::abs(t[r * 8 + p]) > std::abs(t[best * 8 + p])) best = r;
for (int c = 0; c < 8; ++c) std::swap(t[p * 8 + c], t[best * 8 + c]);
const double d = 1.0 / t[p * 8 + p];
for (int c = 0; c < 8; ++c) t[p * 8 + c] *= d;
for (int r = 0; r < 4; ++r) {
if (r == p) continue;
const double f = t[r * 8 + p];
for (int c = 0; c < 8; ++c) t[r * 8 + c] -= f * t[p * 8 + c];
}
}
Mat4 r;
for (int i = 0; i < 4; ++i)
for (int c = 0; c < 4; ++c) r.at(i, c) = float(t[i * 8 + 4 + c]);
return r;
}
Mat4 LookTo(Vec3 eye, Vec3 forward) {
const Vec3 z = Norm(forward);
const Vec3 x = Norm(Cross({0, 1, 0}, z));
const Vec3 y = Cross(z, x);
Mat4 r = Identity();
r.at(0, 0) = x.x; r.at(0, 1) = x.y; r.at(0, 2) = x.z; r.at(0, 3) = -Dot(x, eye);
r.at(1, 0) = y.x; r.at(1, 1) = y.y; r.at(1, 2) = y.z; r.at(1, 3) = -Dot(y, eye);
r.at(2, 0) = z.x; r.at(2, 1) = z.y; r.at(2, 2) = z.z; r.at(2, 3) = -Dot(z, eye);
return r;
}
struct Mesh {
std::vector<Vec3> vertices;
std::vector<std::uint32_t> indices;
Vec3 position{};
bool skinned = false;
Vec3 scale{1, 1, 1};
};
void AddGrid(Mesh& mesh, Vec3 origin, Vec3 du, Vec3 dv, unsigned n) {
const auto base = std::uint32_t(mesh.vertices.size());
for (unsigned j = 0; j <= n; ++j)
for (unsigned i = 0; i <= n; ++i)
mesh.vertices.push_back(origin + du * (float(i) / n) + dv * (float(j) / n));
for (unsigned j = 0; j < n; ++j)
for (unsigned i = 0; i < n; ++i) {
const std::uint32_t a = base + j * (n + 1) + i, b = a + 1, c = a + n + 1, d = c + 1;
mesh.indices.insert(mesh.indices.end(), {a, b, c, b, d, c});
}
}
Mesh Box(Vec3 size, unsigned triangles) {
Mesh mesh;
const auto n = std::max(1u, unsigned(std::sqrt(float(triangles) / 12.0f)));
const Vec3 x{size.x, 0, 0}, y{0, size.y, 0}, z{0, 0, size.z}, o{-size.x / 2, 0, -size.z / 2};
AddGrid(mesh, o, x, y, n);
AddGrid(mesh, o + z, y, x, n);
AddGrid(mesh, o, y, z, n);
AddGrid(mesh, o + x, z, y, n);
AddGrid(mesh, o, z, x, n);
AddGrid(mesh, o + y, x, z, n);
return mesh;
}
Mesh Capsule(float height, float radius, unsigned triangles) {
Mesh mesh;
const auto n = std::max(4u, unsigned(std::sqrt(float(triangles) / 2.0f)));
for (unsigned j = 0; j <= n; ++j) {
const float v = float(j) / n, a = v * 3.14159265f;
for (unsigned i = 0; i <= n; ++i) {
const float u = float(i) / n * 6.2831853f;
const float r = radius * std::sin(a);
mesh.vertices.push_back({r * std::cos(u), height * (0.5f + 0.5f * std::cos(a)), r * std::sin(u)});
}
}
for (unsigned j = 0; j < n; ++j)
for (unsigned i = 0; i < n; ++i) {
const std::uint32_t a = j * (n + 1) + i, b = a + 1, c = a + n + 1, d = c + 1;
mesh.indices.insert(mesh.indices.end(), {a, c, b, b, c, d});
}
return mesh;
}
std::vector<Mesh> MakeScene(unsigned total_triangles, unsigned draws) {
std::mt19937 rng(7);
const auto range = [&](float lo, float hi) { return std::uniform_real_distribution<float>(lo, hi)(rng); };
std::vector<Mesh> scene;
const unsigned character_triangles = 30000, characters = 6;
const unsigned static_triangles = total_triangles - std::min(total_triangles, characters * character_triangles);
const unsigned per_draw = std::max(12u, static_triangles / draws);
Mesh ground = Box({400, 1, 400}, per_draw);
ground.position = {0, -1, 60};
scene.push_back(std::move(ground));
for (unsigned i = 1; i < draws; ++i) {
const unsigned kind = i % 10;
if (kind < 4) {
Mesh m = Box({range(4, 14), range(6, 30), range(4, 14)}, per_draw);
const float side = i % 2 ? 1.0f : -1.0f;
m.position = {side * range(11, 40), 0, range(-20, 220)};
scene.push_back(std::move(m));
} else if (kind < 7) {
Mesh m = Box({0.08f, range(1, 4), 0.08f}, per_draw);
m.position = {range(-9, 9), 0, range(2, 60)};
scene.push_back(std::move(m));
} else if (kind < 8) {
Mesh m = Box({range(0.8f, 2.5f), range(0.5f, 2.0f), range(0.8f, 2.5f)}, per_draw);
m.position = {range(-9, 9), 0, range(3, 90)};
scene.push_back(std::move(m));
} else if (kind < 9) {
Mesh m = Box({range(3, 8), 0.2f, range(1, 3)}, per_draw);
m.position = {range(-10, 10), range(2.5f, 6), range(3, 90)};
scene.push_back(std::move(m));
} else {
Mesh m = Box({0.05f, 0.05f, range(2, 8)}, per_draw);
m.position = {range(-9, 9), range(0.5f, 3), range(2, 60)};
scene.push_back(std::move(m));
}
}
for (unsigned i = 0; i < characters; ++i) {
Mesh m = Capsule(1.8f, 0.3f, character_triangles);
m.position = {range(-5, 5), 0, 4.0f + 4.0f * float(i)};
m.skinned = true;
scene.push_back(std::move(m));
}
return scene;
}
struct Buffer {
VkBuffer buffer{};
VkDeviceMemory memory{};
void* data{};
VkDeviceAddress address{};
VkDeviceSize size{};
};
struct Image {
VkImage image{};
VkDeviceMemory memory{};
VkImageView view{};
};
struct Pipeline {
VkDescriptorSetLayout set{};
VkPipelineLayout layout{};
VkPipeline pipeline{};
};
struct Vk {
VkInstance instance{};
VkPhysicalDevice gpu{};
VkDevice device{};
VkQueue queue{};
VkPhysicalDeviceMemoryProperties memory{};
VkCommandPool pool{};
VkCommandBuffer cmd{};
VkQueryPool stamps{};
VkQueryPool compact_query{};
VkSampler sampler{};
float period = 0;
std::uint64_t mask = ~std::uint64_t{0};
PFN_vkCmdPushDescriptorSetKHR push{};
PFN_vkCreateAccelerationStructureKHR create_as{};
PFN_vkGetAccelerationStructureBuildSizesKHR sizes{};
PFN_vkCmdBuildAccelerationStructuresKHR build{};
PFN_vkGetAccelerationStructureDeviceAddressKHR as_address{};
PFN_vkCmdWriteAccelerationStructuresPropertiesKHR write_props{};
PFN_vkDestroyAccelerationStructureKHR destroy_as{};
char name[256]{};
unsigned Memory(unsigned bits, VkMemoryPropertyFlags flags) {
for (unsigned i = 0; i < memory.memoryTypeCount; ++i)
if ((bits & (1u << i)) && (memory.memoryTypes[i].propertyFlags & flags) == flags) return i;
std::fprintf(stderr, "no memory type\n");
std::exit(1);
}
Buffer MakeBuffer(VkDeviceSize bytes, VkBufferUsageFlags usage, bool host) {
Buffer b;
b.size = bytes;
VkBufferCreateInfo info{.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = bytes,
.usage = usage | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT};
CHECK(vkCreateBuffer(device, &info, nullptr, &b.buffer));
VkMemoryRequirements req;
vkGetBufferMemoryRequirements(device, b.buffer, &req);
VkMemoryAllocateFlagsInfo flags{.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,
.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT};
VkMemoryAllocateInfo alloc{.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = &flags,
.allocationSize = req.size, .memoryTypeIndex = Memory(req.memoryTypeBits,
host ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)};
CHECK(vkAllocateMemory(device, &alloc, nullptr, &b.memory));
CHECK(vkBindBufferMemory(device, b.buffer, b.memory, 0));
if (host) CHECK(vkMapMemory(device, b.memory, 0, VK_WHOLE_SIZE, 0, &b.data));
VkBufferDeviceAddressInfo address{.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, .buffer = b.buffer};
b.address = vkGetBufferDeviceAddress(device, &address);
return b;
}
void Free(Buffer& b) {
if (!b.buffer) return;
if (b.data) vkUnmapMemory(device, b.memory);
vkDestroyBuffer(device, b.buffer, nullptr);
vkFreeMemory(device, b.memory, nullptr);
b = {};
}
Image MakeImage(unsigned w, unsigned h, VkFormat format) {
Image i;
VkImageCreateInfo info{.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D, .format = format, .extent = {w, h, 1},
.mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL, .usage = VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT};
CHECK(vkCreateImage(device, &info, nullptr, &i.image));
VkMemoryRequirements req;
vkGetImageMemoryRequirements(device, i.image, &req);
VkMemoryAllocateInfo alloc{.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = req.size, .memoryTypeIndex = Memory(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)};
CHECK(vkAllocateMemory(device, &alloc, nullptr, &i.memory));
CHECK(vkBindImageMemory(device, i.image, i.memory, 0));
VkImageViewCreateInfo view{.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = i.image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = format,
.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
CHECK(vkCreateImageView(device, &view, nullptr, &i.view));
return i;
}
Pipeline MakePipeline(std::initializer_list<VkDescriptorType> types, unsigned push_bytes,
const std::uint32_t* code, std::size_t code_bytes, VkPipelineCreateFlags create_flags = 0) {
Pipeline p;
std::vector<VkDescriptorSetLayoutBinding> bindings;
for (const auto type : types)
bindings.push_back({unsigned(bindings.size()), type, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr});
VkDescriptorSetLayoutCreateInfo si{.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR,
.bindingCount = unsigned(bindings.size()), .pBindings = bindings.data()};
CHECK(vkCreateDescriptorSetLayout(device, &si, nullptr, &p.set));
VkPushConstantRange range{VK_SHADER_STAGE_COMPUTE_BIT, 0, push_bytes};
VkPipelineLayoutCreateInfo li{.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 1, .pSetLayouts = &p.set, .pushConstantRangeCount = 1, .pPushConstantRanges = &range};
CHECK(vkCreatePipelineLayout(device, &li, nullptr, &p.layout));
VkShaderModule module;
VkShaderModuleCreateInfo mi{.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = code_bytes, .pCode = code};
CHECK(vkCreateShaderModule(device, &mi, nullptr, &module));
VkComputePipelineCreateInfo pc{.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.flags = create_flags, .stage = {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_COMPUTE_BIT, .module = module, .pName = "main"}, .layout = p.layout};
CHECK(vkCreateComputePipelines(device, {}, 1, &pc, nullptr, &p.pipeline));
vkDestroyShaderModule(device, module, nullptr);
return p;
}
Vk() {
VkApplicationInfo app{.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "rt-shadow-test", .apiVersion = VK_API_VERSION_1_3};
VkInstanceCreateInfo ci{.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app};
CHECK(vkCreateInstance(&ci, nullptr, &instance));
unsigned count = 0;
CHECK(vkEnumeratePhysicalDevices(instance, &count, nullptr));
std::vector<VkPhysicalDevice> devices(count);
CHECK(vkEnumeratePhysicalDevices(instance, &count, devices.data()));
gpu = devices.at(0);
for (const auto candidate : devices) {
VkPhysicalDeviceProperties p;
vkGetPhysicalDeviceProperties(candidate, &p);
if (p.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { gpu = candidate; break; }
}
vkGetPhysicalDeviceMemoryProperties(gpu, &memory);
VkPhysicalDeviceProperties properties{};
vkGetPhysicalDeviceProperties(gpu, &properties);
std::strncpy(name, properties.deviceName, sizeof(name) - 1);
period = properties.limits.timestampPeriod;
unsigned family_count = 0, family = 0;
vkGetPhysicalDeviceQueueFamilyProperties(gpu, &family_count, nullptr);
std::vector<VkQueueFamilyProperties> families(family_count);
vkGetPhysicalDeviceQueueFamilyProperties(gpu, &family_count, families.data());
while (family < family_count && !(families[family].queueFlags & VK_QUEUE_COMPUTE_BIT)) ++family;
if (families[family].timestampValidBits < 64) mask = (std::uint64_t{1} << families[family].timestampValidBits) - 1;
const float priority = 1;
VkDeviceQueueCreateInfo qi{.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = family, .queueCount = 1, .pQueuePriorities = &priority};
const std::array<const char*, 4> extensions{
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME,
VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME, VK_KHR_RAY_QUERY_EXTENSION_NAME};
VkPhysicalDeviceRayQueryFeaturesKHR rq{.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR, .rayQuery = VK_TRUE};
VkPhysicalDeviceAccelerationStructureFeaturesKHR as{.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR,
.pNext = &rq, .accelerationStructure = VK_TRUE};
VkPhysicalDeviceVulkan12Features v12{.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
.pNext = &as, .scalarBlockLayout = VK_TRUE, .bufferDeviceAddress = VK_TRUE};
VkPhysicalDeviceFeatures2 f2{.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = &v12};
f2.features.shaderStorageImageExtendedFormats = VK_TRUE;
VkDeviceCreateInfo di{.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = &f2,
.queueCreateInfoCount = 1, .pQueueCreateInfos = &qi,
.enabledExtensionCount = unsigned(extensions.size()), .ppEnabledExtensionNames = extensions.data()};
CHECK(vkCreateDevice(gpu, &di, nullptr, &device));
vkGetDeviceQueue(device, family, 0, &queue);
#define LOAD(field, type, fn) field = reinterpret_cast<type>(vkGetDeviceProcAddr(device, fn))
LOAD(push, PFN_vkCmdPushDescriptorSetKHR, "vkCmdPushDescriptorSetKHR");
LOAD(create_as, PFN_vkCreateAccelerationStructureKHR, "vkCreateAccelerationStructureKHR");
LOAD(sizes, PFN_vkGetAccelerationStructureBuildSizesKHR, "vkGetAccelerationStructureBuildSizesKHR");
LOAD(build, PFN_vkCmdBuildAccelerationStructuresKHR, "vkCmdBuildAccelerationStructuresKHR");
LOAD(as_address, PFN_vkGetAccelerationStructureDeviceAddressKHR, "vkGetAccelerationStructureDeviceAddressKHR");
LOAD(write_props, PFN_vkCmdWriteAccelerationStructuresPropertiesKHR, "vkCmdWriteAccelerationStructuresPropertiesKHR");
LOAD(destroy_as, PFN_vkDestroyAccelerationStructureKHR, "vkDestroyAccelerationStructureKHR");
#undef LOAD
VkCommandPoolCreateInfo pi{.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = family};
CHECK(vkCreateCommandPool(device, &pi, nullptr, &pool));
VkCommandBufferAllocateInfo ai{.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1};
CHECK(vkAllocateCommandBuffers(device, &ai, &cmd));
VkQueryPoolCreateInfo query{.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
.queryType = VK_QUERY_TYPE_TIMESTAMP, .queryCount = 2};
CHECK(vkCreateQueryPool(device, &query, nullptr, &stamps));
VkSamplerCreateInfo sm{.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = VK_FILTER_NEAREST, .minFilter = VK_FILTER_NEAREST,
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE};
CHECK(vkCreateSampler(device, &sm, nullptr, &sampler));
}
void Begin() {
CHECK(vkResetCommandBuffer(cmd, 0));
VkCommandBufferBeginInfo begin{.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
CHECK(vkBeginCommandBuffer(cmd, &begin));
vkCmdResetQueryPool(cmd, stamps, 0, 2);
}
void Stamp(unsigned i) { vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, stamps, i); }
void Barrier() {
VkMemoryBarrier b{.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT};
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &b, 0, nullptr, 0, nullptr);
}
void ToGeneral(VkImage image) {
VkImageMemoryBarrier b{.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = 0, .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image, .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
}
double End() {
CHECK(vkEndCommandBuffer(cmd));
VkSubmitInfo submit{.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &cmd};
CHECK(vkQueueSubmit(queue, 1, &submit, {}));
CHECK(vkQueueWaitIdle(queue));
std::array<std::uint64_t, 2> t{};
const VkResult r = vkGetQueryPoolResults(device, stamps, 0, 2, sizeof t, t.data(), sizeof(std::uint64_t),
VK_QUERY_RESULT_64_BIT);
return r == VK_SUCCESS ? double((t[1] - t[0]) & mask) * period / 1e6 : 0.0;
}
};
struct Blas {
VkAccelerationStructureKHR handle{};
VkDeviceAddress address{};
VkDeviceSize offset{}, bytes{}, scratch{}, update_scratch{};
unsigned triangles{}, vertices{};
VkDeviceSize vertex_offset{}, index_offset{};
};
struct Scene {
Vk& vk;
std::vector<Mesh> meshes;
Buffer geometry{}, staging{}, storage{}, scratch{};
std::vector<Blas> blas;
VkDeviceSize total_bytes = 0, total_scratch = 0;
unsigned triangles = 0;
VkAccelerationStructureGeometryKHR Geometry(const Blas& b) const {
VkAccelerationStructureGeometryKHR g{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR,
.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR, .flags = VK_GEOMETRY_OPAQUE_BIT_KHR};
g.geometry.triangles = {.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR,
.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT, .vertexStride = sizeof(Vec3),
.maxVertex = b.vertices - 1, .indexType = VK_INDEX_TYPE_UINT32};
g.geometry.triangles.vertexData.deviceAddress = geometry.address + b.vertex_offset;
g.geometry.triangles.indexData.deviceAddress = geometry.address + b.index_offset;
return g;
}
void Create(VkBuildAccelerationStructureFlagsKHR flags) {
const VkDeviceSize alignment = 16;
VkDeviceSize bytes = 0;
blas.resize(meshes.size());
for (std::size_t i = 0; i < meshes.size(); ++i) {
blas[i].vertex_offset = bytes;
bytes += (meshes[i].vertices.size() * sizeof(Vec3) + alignment - 1) & ~(alignment - 1);
blas[i].index_offset = bytes;
bytes += (meshes[i].indices.size() * 4 + alignment - 1) & ~(alignment - 1);
blas[i].vertices = unsigned(meshes[i].vertices.size());
blas[i].triangles = unsigned(meshes[i].indices.size() / 3);
triangles += blas[i].triangles;
}
staging = vk.MakeBuffer(bytes, 0, true);
for (std::size_t i = 0; i < meshes.size(); ++i) {
std::memcpy(static_cast<char*>(staging.data) + blas[i].vertex_offset, meshes[i].vertices.data(), meshes[i].vertices.size() * sizeof(Vec3));
std::memcpy(static_cast<char*>(staging.data) + blas[i].index_offset, meshes[i].indices.data(), meshes[i].indices.size() * 4);
}
geometry = vk.MakeBuffer(bytes, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, false);
vk.Begin();
VkBufferCopy copy{0, 0, bytes};
vkCmdCopyBuffer(vk.cmd, staging.buffer, geometry.buffer, 1, &copy);
vk.Barrier();
vk.Stamp(0); vk.Stamp(1);
vk.End();
Sizes(flags);
}
void Sizes(VkBuildAccelerationStructureFlagsKHR flags) {
total_bytes = total_scratch = 0;
for (auto& b : blas) {
const auto g = Geometry(b);
VkAccelerationStructureBuildGeometryInfoKHR info{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, .flags = flags,
.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR, .geometryCount = 1, .pGeometries = &g};
VkAccelerationStructureBuildSizesInfoKHR s{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR};
vk.sizes(vk.device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &info, &b.triangles, &s);
b.offset = total_bytes;
b.bytes = s.accelerationStructureSize;
b.scratch = s.buildScratchSize;
b.update_scratch = s.updateScratchSize;
total_bytes += (s.accelerationStructureSize + AsAlign - 1) & ~(AsAlign - 1);
total_scratch += (std::max(s.buildScratchSize, s.updateScratchSize) + AsAlign - 1) & ~(AsAlign - 1);
}
}
void Allocate() {
for (auto& b : blas) if (b.handle) { vk.destroy_as(vk.device, b.handle, nullptr); b.handle = {}; }
vk.Free(storage);
vk.Free(scratch);
storage = vk.MakeBuffer(total_bytes, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR, false);
scratch = vk.MakeBuffer(total_scratch, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, false);
for (auto& b : blas) {
VkAccelerationStructureCreateInfoKHR ci{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR,
.buffer = storage.buffer, .offset = b.offset, .size = b.bytes,
.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR};
CHECK(vk.create_as(vk.device, &ci, nullptr, &b.handle));
VkAccelerationStructureDeviceAddressInfoKHR ai{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR, .accelerationStructure = b.handle};
b.address = vk.as_address(vk.device, &ai);
}
}
double BuildAll(VkBuildAccelerationStructureFlagsKHR flags) {
std::vector<VkAccelerationStructureGeometryKHR> geometries;
geometries.reserve(blas.size());
std::vector<VkAccelerationStructureBuildGeometryInfoKHR> infos;
std::vector<VkAccelerationStructureBuildRangeInfoKHR> ranges;
std::vector<const VkAccelerationStructureBuildRangeInfoKHR*> pointers;
VkDeviceSize at = 0;
for (auto& b : blas) {
geometries.push_back(Geometry(b));
VkAccelerationStructureBuildGeometryInfoKHR info{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, .flags = flags,
.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR, .dstAccelerationStructure = b.handle,
.geometryCount = 1, .pGeometries = &geometries.back()};
info.scratchData.deviceAddress = scratch.address + at;
at += (std::max(b.scratch, b.update_scratch) + AsAlign - 1) & ~(AsAlign - 1);
infos.push_back(info);
ranges.push_back({b.triangles, 0, 0, 0});
}
for (auto& r : ranges) pointers.push_back(&r);
vk.Begin();
vk.Stamp(0);
vk.build(vk.cmd, unsigned(infos.size()), infos.data(), pointers.data());
vk.Stamp(1);
vk.Barrier();
return vk.End();
}
double Refit(VkBuildAccelerationStructureFlagsKHR flags, bool update) {
std::vector<VkAccelerationStructureGeometryKHR> geometries;
geometries.reserve(blas.size());
std::vector<VkAccelerationStructureBuildGeometryInfoKHR> infos;
std::vector<VkAccelerationStructureBuildRangeInfoKHR> ranges;
std::vector<const VkAccelerationStructureBuildRangeInfoKHR*> pointers;
VkDeviceSize at = 0;
ranges.reserve(blas.size());
for (std::size_t i = 0; i < blas.size(); ++i) {
auto& b = blas[i];
const VkDeviceSize scratch_at = at;
at += (std::max(b.scratch, b.update_scratch) + AsAlign - 1) & ~(AsAlign - 1);
if (!meshes[i].skinned) continue;
geometries.push_back(Geometry(b));
VkAccelerationStructureBuildGeometryInfoKHR info{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, .flags = flags,
.mode = update ? VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR : VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR,
.srcAccelerationStructure = update ? b.handle : VK_NULL_HANDLE, .dstAccelerationStructure = b.handle,
.geometryCount = 1, .pGeometries = &geometries.back()};
info.scratchData.deviceAddress = scratch.address + scratch_at;
infos.push_back(info);
ranges.push_back({b.triangles, 0, 0, 0});
pointers.push_back(&ranges.back());
}
vk.Begin();
vk.Stamp(0);
vk.build(vk.cmd, unsigned(infos.size()), infos.data(), pointers.data());
vk.Stamp(1);
vk.Barrier();
return vk.End();
}
};
struct Tlas {
VkAccelerationStructureKHR handle{};
Buffer instances{}, storage{}, scratch{};
unsigned count = 0;
};
double Median(std::vector<double> v) {
std::sort(v.begin(), v.end());
return v[v.size() / 2];
}
void WriteTlasInstances(Tlas& t, const Scene& scene, const Mat4& view) {
auto* out = static_cast<VkAccelerationStructureInstanceKHR*>(t.instances.data);
for (std::size_t i = 0; i < scene.blas.size(); ++i) {
Mat4 model = Identity();
model.at(0, 3) = scene.meshes[i].position.x;
model.at(1, 3) = scene.meshes[i].position.y;
model.at(2, 3) = scene.meshes[i].position.z;
const Mat4 m = view * model;
VkAccelerationStructureInstanceKHR inst{};
std::memcpy(inst.transform.matrix, m.m.data(), 12 * sizeof(float));
inst.mask = 0xff;
inst.accelerationStructureReference = scene.blas[i].address;
out[i] = inst;
}
}
double BuildTlas(Vk& vk, Tlas& t, const Scene& scene, const Mat4& view, bool time_upload) {
if (time_upload || !t.handle) WriteTlasInstances(t, scene, view);
VkAccelerationStructureGeometryKHR g{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR,
.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR, .flags = VK_GEOMETRY_OPAQUE_BIT_KHR};
g.geometry.instances = {.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR};
g.geometry.instances.data.deviceAddress = t.instances.address;
VkAccelerationStructureBuildGeometryInfoKHR info{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, .flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR,
.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR, .dstAccelerationStructure = t.handle,
.geometryCount = 1, .pGeometries = &g};
info.scratchData.deviceAddress = t.scratch.address;
VkAccelerationStructureBuildRangeInfoKHR range{t.count, 0, 0, 0};
const VkAccelerationStructureBuildRangeInfoKHR* pointer = &range;
vk.Begin();
vk.Stamp(0);
vk.build(vk.cmd, 1, &info, &pointer);
vk.Stamp(1);
vk.Barrier();
return vk.End();
}
Tlas CreateTlas(Vk& vk, const Scene& scene) {
Tlas t;
t.count = unsigned(scene.blas.size());
t.instances = vk.MakeBuffer(t.count * sizeof(VkAccelerationStructureInstanceKHR),
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, true);
VkAccelerationStructureGeometryKHR g{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR,
.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR, .flags = VK_GEOMETRY_OPAQUE_BIT_KHR};
g.geometry.instances = {.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR};
VkAccelerationStructureBuildGeometryInfoKHR info{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, .flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR,
.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR, .geometryCount = 1, .pGeometries = &g};
VkAccelerationStructureBuildSizesInfoKHR s{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR};
vk.sizes(vk.device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &info, &t.count, &s);
t.storage = vk.MakeBuffer(s.accelerationStructureSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR, false);
t.scratch = vk.MakeBuffer(s.buildScratchSize + AsAlign, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, false);
VkAccelerationStructureCreateInfoKHR ci{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR,
.buffer = t.storage.buffer, .size = s.accelerationStructureSize, .type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR};
CHECK(vk.create_as(vk.device, &ci, nullptr, &t.handle));
return t;
}
struct Views {
Mat4 view, proj, inv_proj;
std::array<Mat4, 4> cascade, cascade_inverse;
std::array<float, 3> splits{};
};
Views MakeViews(Vec3 eye, Vec3 forward, Vec3 sun) {
Views v;
v.view = LookTo(eye, forward);
const float fy = 1.0f / std::tan(0.5f * 0.87f), fx = fy * H / W, near = 0.1f, far = 3000.0f;
v.proj.at(0, 0) = fx; v.proj.at(1, 1) = fy;
v.proj.at(2, 2) = far / (far - near); v.proj.at(2, 3) = -near * far / (far - near);
v.proj.at(3, 2) = 1;
v.inv_proj = Inverse(v.proj);
v.splits = {8, 25, 80};
const std::array<float, 4> half{8, 22, 60, 170};
const std::array<float, 4> centers{4, 16, 50, 150};
const Mat4 inv_view = Inverse(v.view);
for (int c = 0; c < 4; ++c) {
const Vec3 center = eye + Norm(forward) * centers[c];
const Mat4 light = LookTo(center + sun * 300.0f, sun * -1.0f);
Mat4 ortho = Identity();
ortho.at(0, 0) = 1.0f / half[c]; ortho.at(1, 1) = 1.0f / half[c];
ortho.at(2, 2) = 1.0f / 800.0f;
Mat4 tile = Identity();
tile.at(0, 0) = 0.25f; tile.at(0, 3) = 0.25f + 0.5f * float(c & 1);
tile.at(1, 1) = 0.25f; tile.at(1, 3) = 0.25f + 0.5f * float(c >> 1);
const Mat4 view_to_atlas = tile * ortho * light * inv_view;
v.cascade_inverse[c] = Inverse(view_to_atlas);
v.cascade[c] = view_to_atlas * v.inv_proj;
}
return v;
}
struct Metric { double mean{}, off10{}, off25{}, signed_bias{}; };
Metric Compare(const std::vector<float>& reference, const std::vector<float>& value, const std::vector<float>& depth) {
Metric m;
std::size_t counted = 0;
for (std::size_t i = 0; i < reference.size(); ++i) {
if (!(depth[i] < 1.0f)) continue;
const double d = double(value[i]) - reference[i];
m.mean += std::abs(d);
m.signed_bias += d;
m.off10 += std::abs(d) > 0.1;
m.off25 += std::abs(d) > 0.25;
++counted;
}
counted = std::max<std::size_t>(counted, 1);
m.mean /= counted; m.signed_bias /= counted;
m.off10 *= 100.0 / counted; m.off25 *= 100.0 / counted;
return m;
}
void WritePgm(const char* path, const std::vector<float>& image) {
std::ofstream out(path, std::ios::binary);
out << "P5\n" << W << ' ' << H << "\n255\n";
std::vector<unsigned char> bytes(image.size());
for (std::size_t i = 0; i < image.size(); ++i) bytes[i] = (unsigned char)(std::clamp(image[i], 0.0f, 1.0f) * 255.0f + 0.5f);
out.write(reinterpret_cast<const char*>(bytes.data()), std::streamsize(bytes.size()));
}
struct PcssPush {
unsigned offsets = 0, frame = 0;
float width = W, height = H, length = 0.6f, thickness = 0.35f, strength = 0.75f, flip = 1;
unsigned debug = 0, contact = 0, pcss = 1;
float light = 0.02f, maximum = 0.3f, minimum = 0.0f, bias = 0.04f;
unsigned filter = 1;
float blend = 0.1f;
};
static_assert(sizeof(PcssPush) == 68);
struct RtPush {
unsigned offsets = 0, frame = 0;
float width = W, height = H;
unsigned rays = 1;
float light = 0.02f, bias = 0.01f, distance = 500.0f;
};
static_assert(sizeof(RtPush) == 32);
struct FilterPush {
unsigned offsets = 0;
int radius = 2;
float width = W, height = H;
unsigned keep_map = 0;
};
struct LabPush {
Mat4 to_view;
float width, height, min_x, min_y, max_x, max_y;
unsigned perspective;
};
struct Lab {
Vk& vk;
Pipeline lab, rt, filter, pcss;
Buffer cb, ub, readback;
Image depth, atlas, raw, shadow;
bool laid_out = false;
explicit Lab(Vk& v) : vk(v) {
using enum VkDescriptorType;
lab = vk.MakePipeline({VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR}, sizeof(LabPush), RT_LAB_COMP, sizeof(RT_LAB_COMP));
rt = vk.MakePipeline({VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR}, sizeof(RtPush), RT_SHADOW_COMP, sizeof(RT_SHADOW_COMP));
filter = vk.MakePipeline({VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER}, sizeof(FilterPush), RT_SHADOW_FILTER_COMP, sizeof(RT_SHADOW_FILTER_COMP));
pcss = vk.MakePipeline({VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER}, sizeof(PcssPush), CONTACT_SHADOW_COMP, sizeof(CONTACT_SHADOW_COMP));
cb = vk.MakeBuffer(4096, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, true);
ub = vk.MakeBuffer(4096, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, true);
readback = vk.MakeBuffer(W * H * 4, 0, true);
depth = vk.MakeImage(W, H, VK_FORMAT_R32_SFLOAT);
atlas = vk.MakeImage(Atlas, Atlas, VK_FORMAT_R32_SFLOAT);
raw = vk.MakeImage(W, H, VK_FORMAT_R8_UNORM);
shadow = vk.MakeImage(W, H, VK_FORMAT_R8G8B8A8_UNORM);
auto* u = static_cast<float*>(ub.data);
u[72] = 2000; u[73] = 1;
}
void LayOut() {
if (laid_out) return;
laid_out = true;
for (auto image : {depth.image, atlas.image, raw.image, shadow.image}) vk.ToGeneral(image);
}
void SetCamera(const Views& v) {
auto* c = static_cast<float*>(cb.data);
for (int r = 0; r < 4; ++r)
for (int k = 0; k < 4; ++k) {
c[r * 4 + k] = v.inv_proj.at(r, k);
for (int cascade = 0; cascade < 4; ++cascade)
c[16 + 16 * cascade + r * 4 + k] = v.cascade[cascade].at(r, k);
}
c[80] = v.splits[0]; c[81] = v.splits[1]; c[82] = v.splits[2];
}
void Lab1(VkAccelerationStructureKHR tlas, Image& target, const Mat4& to_view, float w, float h, std::array<float, 4> region, bool perspective) {
VkDescriptorImageInfo image{{}, target.view, VK_IMAGE_LAYOUT_GENERAL};
VkWriteDescriptorSetAccelerationStructureKHR as{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR,
.accelerationStructureCount = 1, .pAccelerationStructures = &tlas};
const std::array<VkWriteDescriptorSet, 2> writes{{
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 0, .descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .pImageInfo = &image},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .pNext = &as, .dstBinding = 1, .descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR}}};
LabPush push{to_view, w, h, region[0], region[1], region[2], region[3], perspective ? 1u : 0u};
vkCmdBindPipeline(vk.cmd, VK_PIPELINE_BIND_POINT_COMPUTE, lab.pipeline);
vk.push(vk.cmd, VK_PIPELINE_BIND_POINT_COMPUTE, lab.layout, 0, 2, writes.data());
vkCmdPushConstants(vk.cmd, lab.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), &push);
vkCmdDispatch(vk.cmd, (unsigned(w) + 7) / 8, (unsigned(h) + 7) / 8, 1);
vk.Barrier();
}
void Prepare(VkAccelerationStructureKHR tlas, const Views& v) {
SetCamera(v);
vk.Begin();
LayOut();
vk.Barrier();
Lab1(tlas, depth, v.inv_proj, W, H, {0, 0, 1, 1}, true);
for (int c = 0; c < 4; ++c) {
const float x = 0.5f * float(c & 1), y = 0.5f * float(c >> 1);
Lab1(tlas, atlas, v.cascade_inverse[c], Atlas, Atlas, {x, y, x + 0.5f, y + 0.5f}, false);
}
vk.Stamp(0); vk.Stamp(1);
vk.End();
}
void Rt(VkAccelerationStructureKHR tlas, RtPush p) {
VkDescriptorBufferInfo c{cb.buffer, 0, 4096};
VkDescriptorImageInfo d{vk.sampler, depth.view, VK_IMAGE_LAYOUT_GENERAL}, r{{}, raw.view, VK_IMAGE_LAYOUT_GENERAL};
VkWriteDescriptorSetAccelerationStructureKHR as{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR,
.accelerationStructureCount = 1, .pAccelerationStructures = &tlas};
const std::array<VkWriteDescriptorSet, 4> writes{{
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 0, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .pBufferInfo = &c},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 1, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &d},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 2, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .pImageInfo = &r},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .pNext = &as, .dstBinding = 3, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR}}};
vkCmdBindPipeline(vk.cmd, VK_PIPELINE_BIND_POINT_COMPUTE, rt.pipeline);
vk.push(vk.cmd, VK_PIPELINE_BIND_POINT_COMPUTE, rt.layout, 0, 4, writes.data());
vkCmdPushConstants(vk.cmd, rt.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(p), &p);
vkCmdDispatch(vk.cmd, (W + 7) / 8, (H + 7) / 8, 1);
}
void Filter(int radius) {
VkDescriptorBufferInfo c{cb.buffer, 0, 4096}, u{ub.buffer, 0, 4096};
VkDescriptorImageInfo d{vk.sampler, depth.view, VK_IMAGE_LAYOUT_GENERAL}, r{vk.sampler, raw.view, VK_IMAGE_LAYOUT_GENERAL}, s{{}, shadow.view, VK_IMAGE_LAYOUT_GENERAL};
const std::array<VkWriteDescriptorSet, 5> writes{{
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 0, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .pBufferInfo = &c},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 1, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &d},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 2, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &r},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 3, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .pImageInfo = &s},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 4, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .pBufferInfo = &u}}};
FilterPush p;
p.radius = radius;
vkCmdBindPipeline(vk.cmd, VK_PIPELINE_BIND_POINT_COMPUTE, filter.pipeline);
vk.push(vk.cmd, VK_PIPELINE_BIND_POINT_COMPUTE, filter.layout, 0, 5, writes.data());
vkCmdPushConstants(vk.cmd, filter.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(p), &p);
vkCmdDispatch(vk.cmd, (W + 7) / 8, (H + 7) / 8, 1);
}
void Pcss(PcssPush p) {
VkDescriptorBufferInfo c{cb.buffer, 0, 4096}, u{ub.buffer, 0, 4096};
VkDescriptorImageInfo d{vk.sampler, depth.view, VK_IMAGE_LAYOUT_GENERAL}, m{vk.sampler, atlas.view, VK_IMAGE_LAYOUT_GENERAL}, s{{}, shadow.view, VK_IMAGE_LAYOUT_GENERAL};
const std::array<VkWriteDescriptorSet, 5> writes{{
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 0, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .pBufferInfo = &c},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 1, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &d},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 2, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .pImageInfo = &s},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 3, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &m},
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 4, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .pBufferInfo = &u}}};
vkCmdBindPipeline(vk.cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pcss.pipeline);
vk.push(vk.cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pcss.layout, 0, 5, writes.data());
vkCmdPushConstants(vk.cmd, pcss.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(p), &p);
vkCmdDispatch(vk.cmd, (W + 7) / 8, (H + 7) / 8, 1);
}
std::vector<float> Read(Image& image, VkFormat format) {
vk.Begin();
vk.Barrier();
VkBufferImageCopy copy{.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .imageExtent = {W, H, 1}};
vkCmdCopyImageToBuffer(vk.cmd, image.image, VK_IMAGE_LAYOUT_GENERAL, readback.buffer, 1, &copy);
vk.Barrier();
vk.Stamp(0); vk.Stamp(1);
vk.End();
std::vector<float> out(W * H);
const auto* bytes = static_cast<const unsigned char*>(readback.data);
for (std::size_t i = 0; i < out.size(); ++i) {
if (format == VK_FORMAT_R32_SFLOAT) std::memcpy(&out[i], bytes + i * 4, 4);
else if (format == VK_FORMAT_R8_UNORM) out[i] = bytes[i] / 255.0f;
else out[i] = bytes[i * 4 + 3] / 255.0f;
}
return out;
}
void ClearShadow() {
VkClearColorValue color{};
color.float32[0] = color.float32[1] = color.float32[2] = color.float32[3] = 1;
VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
vkCmdClearColorImage(vk.cmd, shadow.image, VK_IMAGE_LAYOUT_GENERAL, &color, 1, &range);
vk.Barrier();
}
};
}
int main(int argc, char** argv) {
std::setvbuf(stdout, nullptr, _IONBF, 0);
const char* dump = argc > 1 ? argv[1] : nullptr;
Vk vk;
std::printf("GPU: %s, %ux%u, atlas %u\n", vk.name, W, H, Atlas);
Lab lab(vk);
vk.MakePipeline({VK_DESCRIPTOR_TYPE_STORAGE_BUFFER}, 56, RT_UNPROJECT_COMP, sizeof(RT_UNPROJECT_COMP));
vk.MakePipeline({VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE},
64, POINT_SHADOW_RAY_COMP, sizeof(POINT_SHADOW_RAY_COMP));
vk.MakePipeline({VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR},
128, SSGI_GATHER_RAY_COMP, sizeof(SSGI_GATHER_RAY_COMP));
if (argc > 1 && std::strcmp(argv[1], "--pipelines") == 0) {
std::puts("RT pipelines: built");
return 0;
}
const Vec3 sun = Norm({-0.5f, 0.75f, 0.4f});
constexpr auto Trace = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
constexpr auto Fast = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR;
constexpr auto Update = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR;
const std::array<std::array<Vec3, 2>, 3> cameras{{
{{{0, 1.6f, -2}, {0.1f, -0.05f, 1}}},
{{{-4, 1.7f, 12}, {0.8f, -0.25f, 0.5f}}},
{{{5, 3, 30}, {-0.5f, -0.5f, 1}}},
}};
if (argc > 1 && std::strcmp(argv[1], "--speed") == 0) {
const unsigned total = argc > 2 ? unsigned(std::strtoul(argv[2], nullptr, 10)) : 1500000u;
Scene scene{vk, MakeScene(total, total / 1875)};
scene.Create(Trace | Update);
scene.Allocate();
scene.BuildAll(Trace | Update);
const unsigned n = unsigned(scene.blas.size());
std::printf("== Speed: %u triangles in %u BLAS\n", scene.triangles, n);
struct Variant { const char* name; VkBuildAccelerationStructureFlagsKHR flags; bool doubled; };
const std::array<Variant, 3> variants{{{"TLAS fast build", Fast, false},
{"TLAS fast trace", Trace, false},
{"TLAS fast build, every mesh twice", Fast, true}}};
const auto make_tlas = [&](unsigned count, VkBuildAccelerationStructureFlagsKHR flags) {
Tlas t;
t.count = count;
t.instances = vk.MakeBuffer(count * sizeof(VkAccelerationStructureInstanceKHR),
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, true);
VkAccelerationStructureGeometryKHR g{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR,
.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR, .flags = VK_GEOMETRY_OPAQUE_BIT_KHR};
g.geometry.instances = {.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR};
VkAccelerationStructureBuildGeometryInfoKHR info{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, .flags = flags,
.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR, .geometryCount = 1, .pGeometries = &g};
VkAccelerationStructureBuildSizesInfoKHR s{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR};
vk.sizes(vk.device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &info, &t.count, &s);
t.storage = vk.MakeBuffer(s.accelerationStructureSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR, false);
t.scratch = vk.MakeBuffer(s.buildScratchSize + AsAlign, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, false);
VkAccelerationStructureCreateInfoKHR ci{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR,
.buffer = t.storage.buffer, .size = s.accelerationStructureSize, .type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR};
CHECK(vk.create_as(vk.device, &ci, nullptr, &t.handle));
return t;
};
const auto build_tlas = [&](Tlas& t, const Mat4& view, VkBuildAccelerationStructureFlagsKHR flags, bool doubled) {
auto* out = static_cast<VkAccelerationStructureInstanceKHR*>(t.instances.data);
for (unsigned copy = 0; copy < (doubled ? 2u : 1u); ++copy) {
for (unsigned i = 0; i < n; ++i) {
Mat4 model = Identity();
model.at(0, 3) = scene.meshes[i].position.x;
model.at(1, 3) = scene.meshes[i].position.y;
model.at(2, 3) = scene.meshes[i].position.z;
const Mat4 m = view * model;
VkAccelerationStructureInstanceKHR inst{};
std::memcpy(inst.transform.matrix, m.m.data(), 12 * sizeof(float));
inst.mask = doubled ? (copy ? 2 : 1) : 3;
inst.accelerationStructureReference = scene.blas[i].address;
out[copy * n + i] = inst;
}
}
VkAccelerationStructureGeometryKHR g{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR,
.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR, .flags = VK_GEOMETRY_OPAQUE_BIT_KHR};
g.geometry.instances = {.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR};
g.geometry.instances.data.deviceAddress = t.instances.address;
VkAccelerationStructureBuildGeometryInfoKHR info{.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR,
.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, .flags = flags,
.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR, .dstAccelerationStructure = t.handle,
.geometryCount = 1, .pGeometries = &g};
info.scratchData.deviceAddress = t.scratch.address;
VkAccelerationStructureBuildRangeInfoKHR range{doubled ? 2 * n : n, 0, 0, 0};
const VkAccelerationStructureBuildRangeInfoKHR* pointer = &range;
vk.Begin();
vk.Stamp(0);
vk.build(vk.cmd, 1, &info, &pointer);
vk.Stamp(1);
vk.Barrier();
return vk.End();
};
struct LampPush {
unsigned first_light = 0, frame = 0;
float width = W, height = H, length = 0, thickness = 0, strength = 1, fade_start = 60, fade_end = 100;
unsigned count = 1, steps = 0;
float bias = 0.02f;
std::array<int, 4> rect{};
};
static_assert(sizeof(LampPush) == 64);
Pipeline lamp = vk.MakePipeline({VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE},
64, POINT_SHADOW_RAY_COMP, sizeof(POINT_SHADOW_RAY_COMP),
VK_PIPELINE_CREATE_DISPATCH_BASE_BIT);
Buffer lights = vk.MakeBuffer(64 * 1024 * 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, true);
Image zero_image = vk.MakeImage(W, H, VK_FORMAT_B10G11R11_UFLOAT_PACK32);
std::array<Image, 2> history{vk.MakeImage(W, H, VK_FORMAT_R16G16B16A16_SFLOAT),
vk.MakeImage(W, H, VK_FORMAT_R16G16B16A16_SFLOAT)};
std::array<Image, 4> lamp_images{};
for (auto& image : lamp_images) image = vk.MakeImage(W, H, VK_FORMAT_B10G11R11_UFLOAT_PACK32);
vk.Begin();
for (auto& image : lamp_images) vk.ToGeneral(image.image);
vk.ToGeneral(zero_image.image);
for (auto& image : history) vk.ToGeneral(image.image);
{
VkClearColorValue zero{};
const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
vkCmdClearColorImage(vk.cmd, zero_image.image, VK_IMAGE_LAYOUT_GENERAL, &zero, 1, &range);
}
vk.Stamp(0); vk.Stamp(1);
vk.End();
const auto decode = [](std::uint32_t v) {
const unsigned e = (v >> 6) & 31u, m = v & 63u;
return e == 0 ? float(m) / 64.0f * std::ldexp(1.0f, -14) : std::ldexp(1.0f + float(m) / 64.0f, int(e) - 15);
};
std::vector<std::array<double, 3>> lamp_ms(variants.size()), sun_ms(variants.size());
std::array<double, 3> per_tile_ms{}, pick_ms{}, reuse_ms{}, reuse_error{};
std::array<std::array<double, 2>, 3> pick_error{};
std::vector<double> tlas_ms(variants.size());
double lit_sum = 0;
unsigned tile_sum = 0;
for (std::size_t cam = 0; cam < cameras.size(); ++cam) {
const Views views = MakeViews(cameras[cam][0], cameras[cam][1], sun);
std::vector<LampPush> tiles;
auto* words = static_cast<float*>(lights.data);
for (int r = 0; r < 4; ++r)
for (int k = 0; k < 4; ++k) words[r * 4 + k] = views.inv_proj.at(r, k);
unsigned used = 16;
const float radius = 9.0f;
for (float z = 2.0f; z < 100.0f; z += 6.5f) {
for (const float x : {-7.0f, 7.0f, 0.0f}) {
if (x == 0.0f && int(z) % 3 != 0) continue;
const Vec3 world{x, x == 0.0f ? 1.2f : 3.5f, z + (x > 0 ? 3.0f : 0.0f)};
Vec3 v{};
for (int r = 0; r < 3; ++r) {
const float value = views.view.at(r, 0) * world.x + views.view.at(r, 1) * world.y +
views.view.at(r, 2) * world.z + views.view.at(r, 3);
(r == 0 ? v.x : r == 1 ? v.y : v.z) = value;
}
if (v.z < -radius) continue;
float x0 = 0, y0 = 0, x1 = float(W), y1 = float(H);
if (v.z - radius > 0.1f) {
x0 = float(W); y0 = float(H); x1 = 0; y1 = 0;
for (int c = 0; c < 8; ++c) {
const Vec3 corner{v.x + (c & 1 ? radius : -radius), v.y + (c & 2 ? radius : -radius),
v.z + (c & 4 ? radius : -radius)};
const float nx = views.proj.at(0, 0) * corner.x / corner.z;
const float ny = views.proj.at(1, 1) * corner.y / corner.z;
const float px = (nx * 0.5f + 0.5f) * W, py = (0.5f - ny * 0.5f) * H;
x0 = std::min(x0, px); x1 = std::max(x1, px);
y0 = std::min(y0, py); y1 = std::max(y1, py);
}
}
LampPush push;
push.rect = {std::clamp(int(x0), 0, int(W)), std::clamp(int(y0), 0, int(H)),
std::clamp(int(x1) + 1, 0, int(W)), std::clamp(int(y1) + 1, 0, int(H))};
if (push.rect[0] >= push.rect[2] || push.rect[1] >= push.rect[3]) continue;
push.first_light = used;
const std::array<float, 16> light{v.x, v.y, v.z, 1.0f / (radius * radius), 1.0f, 0.8f, 0.6f};
std::memcpy(words + used, light.data(), sizeof(light));
used += 16;
tiles.push_back(push);
}
}
tile_sum += unsigned(tiles.size());
const unsigned table = used;
std::array<int, 4> covered{int(W), int(H), 0, 0};
for (const LampPush& tile : tiles) {
const std::array<std::uint32_t, 8> row{unsigned(tile.rect[0]), unsigned(tile.rect[1]), unsigned(tile.rect[2]),
unsigned(tile.rect[3]), tile.first_light, 1u, 0u, 0u};
std::memcpy(words + used, row.data(), sizeof(row));
used += 8;
covered = {std::min(covered[0], tile.rect[0]), std::min(covered[1], tile.rect[1]),
std::max(covered[2], tile.rect[2]), std::max(covered[3], tile.rect[3])};
}
for (std::size_t at = 0; at < variants.size(); ++at) {
const Variant& variant = variants[at];
Tlas tlas = make_tlas(variant.doubled ? 2 * n : n, variant.flags);
std::vector<double> builds;
for (int i = 0; i < 9; ++i) builds.push_back(build_tlas(tlas, views.view, variant.flags, variant.doubled));
tlas_ms[at] += Median(builds) / cameras.size();
if (at == 0) {
lab.Prepare(tlas.handle, views);
}
std::vector<double> sun_samples, lamp_samples;
for (int i = 0; i < 15; ++i) {
vk.Begin();
vk.Stamp(0);
RtPush p;
p.rays = 2;
lab.Rt(tlas.handle, p);
vk.Stamp(1);
vk.Barrier();
sun_samples.push_back(vk.End());
}
const unsigned history_at = used;
const auto run_lamps = [&](bool per_tile, unsigned pick = 0, unsigned frame = 0, int parity = -1) {
if (parity >= 0) {
auto* block = reinterpret_cast<std::uint32_t*>(words) + history_at;
block[0] = frame == 0 ? 2u : 3u;
block[1] = frame;
const std::array<float, 12> identity{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0};
std::memcpy(block + 2, identity.data(), sizeof(identity));
std::memcpy(block + 14, views.proj.m.data(), 64);
}
vk.Begin();
VkClearColorValue one{};
one.float32[0] = one.float32[1] = one.float32[2] = 1.0f;
const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
vkCmdClearColorImage(vk.cmd, lamp_images[0].image, VK_IMAGE_LAYOUT_GENERAL, &one, 1, &range);
vkCmdClearColorImage(vk.cmd, lamp_images[2].image, VK_IMAGE_LAYOUT_GENERAL, &one, 1, &range);
vk.Barrier();
VkDescriptorBufferInfo data{lights.buffer, 0, (used + 32) * 4};
VkDescriptorImageInfo depth{vk.sampler, lab.depth.view, VK_IMAGE_LAYOUT_GENERAL};
std::array<VkDescriptorImageInfo, 4> images{};
for (int k = 0; k < 4; ++k) images[k] = {{}, lamp_images[k].view, VK_IMAGE_LAYOUT_GENERAL};
VkWriteDescriptorSetAccelerationStructureKHR as{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR,
.accelerationStructureCount = 1, .pAccelerationStructures = &tlas.handle};
const int cur_history = parity < 0 ? 0 : parity;
VkDescriptorImageInfo history_prev{{}, history[cur_history ^ 1].view, VK_IMAGE_LAYOUT_GENERAL};
VkDescriptorImageInfo history_cur{{}, history[cur_history].view, VK_IMAGE_LAYOUT_GENERAL};
std::array<VkWriteDescriptorSet, 9> writes{};
for (unsigned k = 0; k < 9; ++k) writes[k] = {.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = k, .descriptorCount = 1};
writes[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; writes[0].pBufferInfo = &data;
writes[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; writes[1].pImageInfo = &depth;
for (int k = 0; k < 4; ++k) { writes[2 + k].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; writes[2 + k].pImageInfo = &images[k]; }
writes[6].descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; writes[6].pNext = &as;
writes[7].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; writes[7].pImageInfo = &history_prev;
writes[8].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; writes[8].pImageInfo = &history_cur;
const auto snapshot = [&](std::array<int, 4> r) {
const VkImageCopy region{.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .srcOffset = {r[0], r[1], 0},
.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .dstOffset = {r[0], r[1], 0},
.extent = {unsigned(r[2] - r[0]), unsigned(r[3] - r[1]), 1}};
vkCmdCopyImage(vk.cmd, zero_image.image, VK_IMAGE_LAYOUT_GENERAL, lamp_images[1].image, VK_IMAGE_LAYOUT_GENERAL, 1, &region);
vkCmdCopyImage(vk.cmd, zero_image.image, VK_IMAGE_LAYOUT_GENERAL, lamp_images[3].image, VK_IMAGE_LAYOUT_GENERAL, 1, &region);
vk.Barrier();
};
const auto dispatch = [&](LampPush push) {
vkCmdBindPipeline(vk.cmd, VK_PIPELINE_BIND_POINT_COMPUTE, lamp.pipeline);
vk.push(vk.cmd, VK_PIPELINE_BIND_POINT_COMPUTE, lamp.layout, 0, 9, writes.data());
vkCmdPushConstants(vk.cmd, lamp.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), &push);
const unsigned gx0 = unsigned(push.rect[0]) / 8, gy0 = unsigned(push.rect[1]) / 8;
const unsigned gx1 = (unsigned(push.rect[2]) + 7) / 8, gy1 = (unsigned(push.rect[3]) + 7) / 8;
vkCmdDispatchBase(vk.cmd, gx0, gy0, 0, gx1 - gx0, gy1 - gy0, 1);
vk.Barrier();
};
vk.Stamp(0);
if (parity >= 0) {
VkClearColorValue zero{};
vkCmdClearColorImage(vk.cmd, history[cur_history].image, VK_IMAGE_LAYOUT_GENERAL, &zero, 1, &range);
vk.Barrier();
}
if (per_tile) {
for (std::size_t t = 0; t < tiles.size(); ++t) {
snapshot(tiles[t].rect);
LampPush push = tiles[t];
push.first_light = table + unsigned(t) * 8;
push.count = 1;
dispatch(push);
}
} else if (!tiles.empty()) {
snapshot({0, 0, int(W), int(H)});
LampPush push = tiles[0];
push.first_light = table;
push.count = unsigned(tiles.size());
push.rect = covered;
push.steps = pick;
push.frame = frame;
if (parity >= 0) std::memcpy(&push.length, &history_at, 4);
dispatch(push);
}
vk.Stamp(1);
vk.Barrier();
return vk.End();
};
if (at == 1) {
std::vector<double> per_tile;
for (int i = 0; i < 15; ++i) per_tile.push_back(run_lamps(true));
per_tile_ms[cam] = Median(per_tile);
const auto lamp_values = [&] {
const auto raw = lab.Read(lamp_images[0], VK_FORMAT_R32_SFLOAT);
std::vector<float> out(raw.size());
for (std::size_t k = 0; k < raw.size(); ++k) {
std::uint32_t bits;
std::memcpy(&bits, &raw[k], 4);
out[k] = decode(bits);
}
return out;
};
run_lamps(false);
const auto exact = lamp_values();
std::vector<double> picked;
for (int i = 0; i < 15; ++i) picked.push_back(run_lamps(false, 1, unsigned(i)));
pick_ms[cam] = Median(picked);
std::vector<float> average(exact.size(), 0.0f);
double one_frame = 0, sixteen = 0;
for (unsigned f = 0; f < 16; ++f) {
run_lamps(false, 1, f);
const auto one = lamp_values();
for (std::size_t k = 0; k < one.size(); ++k) {
average[k] += one[k] / 16.0f;
if (f == 0) one_frame += std::abs(double(one[k]) - exact[k]);
}
}
for (std::size_t k = 0; k < exact.size(); ++k) sixteen += std::abs(double(average[k]) - exact[k]);
pick_error[cam] = {one_frame / double(exact.size()), sixteen / double(exact.size())};
std::vector<double> reused;
std::vector<float> reuse_average(exact.size(), 0.0f);
for (unsigned f = 0; f < 24; ++f) {
const double ms = run_lamps(false, 1, f, int(f & 1u));
if (f >= 8) reused.push_back(ms);
if (f >= 8) {
const auto one = lamp_values();
for (std::size_t k = 0; k < one.size(); ++k) reuse_average[k] += one[k] / 16.0f;
}
}
reuse_ms[cam] = Median(reused);
double reuse_sum = 0;
for (std::size_t k = 0; k < exact.size(); ++k) reuse_sum += std::abs(double(reuse_average[k]) - exact[k]);
reuse_error[cam] = reuse_sum / double(exact.size());
}
for (int i = 0; i < 15; ++i) lamp_samples.push_back(run_lamps(false));
sun_ms[at][cam] = Median(sun_samples);
lamp_ms[at][cam] = Median(lamp_samples);
if (at == 0) {
const auto out = lab.Read(lamp_images[0], VK_FORMAT_R32_SFLOAT);
double lit = 0;
for (const float f : out) {
std::uint32_t bits;
std::memcpy(&bits, &f, 4);
lit += decode(bits);
}
lit_sum += lit / double(out.size()) / cameras.size();
}
vk.destroy_as(vk.device, tlas.handle, nullptr);
vk.Free(tlas.instances); vk.Free(tlas.storage); vk.Free(tlas.scratch);
}
}
std::printf("%u lamp tiles over %zu cameras, mean lamp light kept %.4f\n", tile_sum, cameras.size(), lit_sum);
std::printf("Lamps on the fast-trace TLAS, a snapshot, dispatch and barriers per tile: %.3f %.3f %.3f ms\n",
per_tile_ms[0], per_tile_ms[1], per_tile_ms[2]);
std::printf("Lamps, strongest lamp plus one picked by weight: %.3f %.3f %.3f ms; mean error against every lamp "
"%.4f %.4f %.4f in one frame, %.4f %.4f %.4f over 16 frames\n",
pick_ms[0], pick_ms[1], pick_ms[2], pick_error[0][0], pick_error[1][0], pick_error[2][0],
pick_error[0][1], pick_error[1][1], pick_error[2][1]);
std::printf("Lamps, picked, reusing still 8x8 blocks (still camera, frames 8-23): %.3f %.3f %.3f ms; mean error "
"against every lamp over 16 frames %.4f %.4f %.4f\n",
reuse_ms[0], reuse_ms[1], reuse_ms[2], reuse_error[0], reuse_error[1], reuse_error[2]);
std::printf("%-36s %9s %21s %21s\n", "", "TLAS ms", "sun 2 rays ms", "lamp rays ms");
for (std::size_t at = 0; at < variants.size(); ++at) {
std::printf("%-36s %9.3f %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f\n", variants[at].name, tlas_ms[at],
sun_ms[at][0], sun_ms[at][1], sun_ms[at][2], lamp_ms[at][0], lamp_ms[at][1], lamp_ms[at][2]);
}
return 0;
}
for (const auto [total, draws] : std::array<std::array<unsigned, 2>, 3>{{{500000, 400}, {1500000, 800}, {4000000, 1200}}}) {
Scene scene{vk, MakeScene(total, draws)};
scene.Create(Trace | Update);
std::printf("\n== Scene: %u triangles in %zu BLAS\n", scene.triangles, scene.blas.size());
const auto report_builds = [&](const char* name, VkBuildAccelerationStructureFlagsKHR flags) {
scene.Sizes(flags);
scene.Allocate();
std::vector<double> times;
for (int i = 0; i < 5; ++i) times.push_back(scene.BuildAll(flags));
std::printf("All BLAS %-22s build %8.2f ms, %7.1f MB, scratch %7.1f MB\n", name, Median(times),
double(scene.total_bytes) / 1048576.0, double(scene.total_scratch) / 1048576.0);
};
report_builds("fast build", Fast);
report_builds("fast trace", Trace);
report_builds("fast build + update", Fast | Update);
{
std::vector<double> refit, rebuild;
for (int i = 0; i < 9; ++i) refit.push_back(scene.Refit(Fast | Update, true));
for (int i = 0; i < 9; ++i) rebuild.push_back(scene.Refit(Fast | Update, false));
std::printf("6 characters, 180k triangles: refit %.3f ms, rebuild %.3f ms\n", Median(refit), Median(rebuild));
}
for (const bool fast_blas : {false, true}) {
const VkBuildAccelerationStructureFlagsKHR flags = fast_blas ? Fast : Trace;
scene.Sizes(flags);
scene.Allocate();
scene.BuildAll(flags);
Tlas tlas = CreateTlas(vk, scene);
std::array<double, 5> sums{};
std::array<Metric, 6> metrics{};
double tlas_ms = 0;
for (std::size_t cam = 0; cam < cameras.size(); ++cam) {
const Views views = MakeViews(cameras[cam][0], cameras[cam][1], sun);
std::vector<double> times;
for (int i = 0; i < 9; ++i) times.push_back(BuildTlas(vk, tlas, scene, views.view, true));
tlas_ms += Median(times) / cameras.size();
lab.Prepare(tlas.handle, views);
const auto depth = lab.Read(lab.depth, VK_FORMAT_R32_SFLOAT);
if (fast_blas) continue;
const auto covered = std::count_if(depth.begin(), depth.end(), [](float d) { return d < 1.0f; });
if (covered < depth.size() / 4) {
std::fprintf(stderr, "camera %zu sees geometry in only %lld pixels\n", cam, (long long)covered);
return 1;
}
std::vector<float> truth(W * H, 0.0f);
for (unsigned frame = 0; frame < 64; ++frame) {
vk.Begin();
RtPush p;
p.frame = frame;
p.rays = 16;
lab.Rt(tlas.handle, p);
vk.Barrier();
vk.Stamp(0); vk.Stamp(1);
vk.End();
const auto one = lab.Read(lab.raw, VK_FORMAT_R8_UNORM);
for (std::size_t i = 0; i < truth.size(); ++i) truth[i] += one[i] / 64.0f;
}
const auto timed = [&](auto&& body) {
std::vector<double> samples;
for (int i = 0; i < 16; ++i) {
vk.Begin();
vk.Stamp(0);
body();
vk.Stamp(1);
vk.Barrier();
samples.push_back(vk.End());
}
return Median(samples);
};
const auto add = [&](Metric& into, const Metric& m) {
into.mean += m.mean / cameras.size(); into.off10 += m.off10 / cameras.size();
into.off25 += m.off25 / cameras.size(); into.signed_bias += m.signed_bias / cameras.size();
};
vk.Begin(); lab.ClearShadow(); vk.Stamp(0); vk.Stamp(1); vk.End();
sums[0] += timed([&] { lab.Pcss({}); }) / cameras.size();
vk.Begin(); lab.ClearShadow(); vk.Stamp(0); vk.Stamp(1); vk.End();
vk.Begin(); lab.Pcss({}); vk.Barrier(); vk.Stamp(0); vk.Stamp(1); vk.End();
const auto pcss = lab.Read(lab.shadow, VK_FORMAT_R8G8B8A8_UNORM);
add(metrics[0], Compare(truth, pcss, depth));
unsigned at = 1;
std::vector<float> rt_best;
for (unsigned rays : {1u, 2u, 4u, 8u}) {
RtPush p;
p.rays = rays;
sums[at] += timed([&] { lab.Rt(tlas.handle, p); }) / cameras.size();
vk.Begin(); lab.Rt(tlas.handle, p); vk.Barrier(); vk.Stamp(0); vk.Stamp(1); vk.End();
vk.Begin(); lab.ClearShadow(); lab.Filter(2); vk.Barrier(); vk.Stamp(0); vk.Stamp(1); vk.End();
const auto out = lab.Read(lab.shadow, VK_FORMAT_R8G8B8A8_UNORM);
add(metrics[at], Compare(truth, out, depth));
if (rays == 4) rt_best = out;
++at;
}
metrics[5].off10 += timed([&] { lab.Filter(2); }) / cameras.size();
if (dump && total == 1500000 && cam < 3) {
const std::string base = std::string(dump) + "/cam" + std::to_string(cam);
WritePgm((base + "_truth.pgm").c_str(), truth);
WritePgm((base + "_pcss.pgm").c_str(), pcss);
WritePgm((base + "_rt4.pgm").c_str(), rt_best);
}
}
std::printf("TLAS %zu instances on %s BLAS: %.3f ms\n", scene.blas.size(), fast_blas ? "fast-build" : "fast-trace", tlas_ms);
if (fast_blas) {
const Views views = MakeViews(cameras[0][0], cameras[0][1], sun);
BuildTlas(vk, tlas, scene, views.view, true);
lab.Prepare(tlas.handle, views);
std::vector<double> samples;
for (int i = 0; i < 16; ++i) {
vk.Begin();
vk.Stamp(0);
RtPush p;
p.rays = 2;
lab.Rt(tlas.handle, p);
vk.Stamp(1);
vk.Barrier();
samples.push_back(vk.End());
}
std::printf("2 rays per pixel on fast-build BLAS, camera 0: %.3f ms\n", Median(samples));
} else {
std::printf("%-20s %8s %9s %8s %8s %8s\n", "Against 1024 rays", "GPU ms", "mean err", ">0.10 %", ">0.25 %", "bias");
std::printf("%-20s %8.3f %9.4f %8.2f %8.2f %+8.4f\n", "PCSS 4096 atlas", sums[0], metrics[0].mean, metrics[0].off10, metrics[0].off25, metrics[0].signed_bias);
unsigned at = 1;
for (unsigned rays : {1u, 2u, 4u, 8u}) {
std::printf("RT %u rays + filter %8.3f %9.4f %8.2f %8.2f %+8.4f\n", rays, sums[at] + metrics[5].off10, metrics[at].mean, metrics[at].off10, metrics[at].off25, metrics[at].signed_bias);
++at;
}
std::printf("Filter alone: %.3f ms\n", metrics[5].off10);
}
vk.destroy_as(vk.device, tlas.handle, nullptr);
vk.Free(tlas.instances); vk.Free(tlas.storage); vk.Free(tlas.scratch);
}
for (auto& b : scene.blas) if (b.handle) vk.destroy_as(vk.device, b.handle, nullptr);
vk.Free(scene.storage); vk.Free(scene.scratch); vk.Free(scene.geometry); vk.Free(scene.staging);
}
return 0;
}