mirror of
https://github.com/Awakenchan/LanternEngine.git
synced 2026-11-10 17:02:57 -10:00
478 lines
25 KiB
C++
478 lines
25 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
#include <vulkan/vulkan.h>
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <vector>
|
|
#include "video_core/host_shaders/contact_shadow_comp.h"
|
|
|
|
namespace {
|
|
#define CHECK(call) do { if ((call) != VK_SUCCESS) { std::fprintf(stderr, "%s failed\n", #call); std::exit(1); } } while (false)
|
|
unsigned W = 17, H = 9;
|
|
constexpr unsigned Atlas = 64, CbBase = 4, UbBase = 8;
|
|
struct Push {
|
|
unsigned offsets = (CbBase * 4) | ((UbBase * 4) << 8), frame = 0;
|
|
float width = W, height = H, length = 0.6f, thickness = 0.35f, strength = 0.75f, flip = 1;
|
|
unsigned debug = 0, contact = 0, pcss = 0;
|
|
float light = 0.02f, maximum = 0.3f, minimum = 0.0f, bias = 0.04f;
|
|
unsigned filter = 1;
|
|
float blend = 0;
|
|
unsigned optimizations = 0;
|
|
};
|
|
static_assert(sizeof(Push) == 72);
|
|
constexpr unsigned PushBytes = 68;
|
|
struct Buffer { VkBuffer buffer{}; VkDeviceMemory memory{}; void* data{}; };
|
|
struct Image { VkImage image{}; VkDeviceMemory memory{}; VkImageView view{}; };
|
|
|
|
struct Test {
|
|
VkInstance instance{};
|
|
VkPhysicalDevice gpu{};
|
|
VkDevice device{};
|
|
VkQueue queue{};
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
VkCommandPool pool{};
|
|
VkCommandBuffer cmd{};
|
|
VkDescriptorSetLayout set_layout{};
|
|
VkPipelineLayout layout{};
|
|
VkPipeline pipeline{};
|
|
VkPipeline optimized_pipeline{};
|
|
VkSampler sampler{};
|
|
VkQueryPool timing{};
|
|
float timestamp_period = 0;
|
|
unsigned timestamp_bits = 0;
|
|
PFN_vkCmdPushDescriptorSetKHR descriptors{};
|
|
std::vector<Buffer> buffers;
|
|
std::vector<Image> images;
|
|
|
|
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::abort();
|
|
}
|
|
Buffer MakeBuffer(unsigned bytes) {
|
|
Buffer b;
|
|
VkBufferCreateInfo info{.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = bytes,
|
|
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
|
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT};
|
|
CHECK(vkCreateBuffer(device, &info, nullptr, &b.buffer));
|
|
VkMemoryRequirements req;
|
|
vkGetBufferMemoryRequirements(device, b.buffer, &req);
|
|
VkMemoryAllocateInfo alloc{.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
|
.allocationSize = req.size, .memoryTypeIndex = Memory(req.memoryTypeBits,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)};
|
|
CHECK(vkAllocateMemory(device, &alloc, nullptr, &b.memory));
|
|
CHECK(vkBindBufferMemory(device, b.buffer, b.memory, 0));
|
|
CHECK(vkMapMemory(device, b.memory, 0, VK_WHOLE_SIZE, 0, &b.data));
|
|
std::memset(b.data, 0, bytes);
|
|
buffers.push_back(b);
|
|
return 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));
|
|
images.push_back(i);
|
|
return i;
|
|
}
|
|
Test() {
|
|
VkApplicationInfo app{.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
|
.pApplicationName = "directional-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()));
|
|
assert(count);
|
|
gpu = devices[0];
|
|
vkGetPhysicalDeviceMemoryProperties(gpu, &memory);
|
|
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;
|
|
assert(family < family_count);
|
|
timestamp_bits = families[family].timestampValidBits;
|
|
VkPhysicalDeviceProperties properties{};
|
|
vkGetPhysicalDeviceProperties(gpu, &properties);
|
|
timestamp_period = properties.limits.timestampPeriod;
|
|
const float priority = 1;
|
|
VkDeviceQueueCreateInfo qi{.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
|
.queueFamilyIndex = family, .queueCount = 1, .pQueuePriorities = &priority};
|
|
const char* extension = VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME;
|
|
VkDeviceCreateInfo di{.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
|
.queueCreateInfoCount = 1, .pQueueCreateInfos = &qi,
|
|
.enabledExtensionCount = 1, .ppEnabledExtensionNames = &extension};
|
|
CHECK(vkCreateDevice(gpu, &di, nullptr, &device));
|
|
vkGetDeviceQueue(device, family, 0, &queue);
|
|
descriptors = reinterpret_cast<PFN_vkCmdPushDescriptorSetKHR>(
|
|
vkGetDeviceProcAddr(device, "vkCmdPushDescriptorSetKHR"));
|
|
assert(descriptors);
|
|
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));
|
|
const std::array<VkDescriptorType, 5> types{
|
|
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};
|
|
std::array<VkDescriptorSetLayoutBinding, 5> bindings{};
|
|
for (unsigned i = 0; i < 5; ++i) bindings[i] = {i, types[i], 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 = 5, .pBindings = bindings.data()};
|
|
CHECK(vkCreateDescriptorSetLayout(device, &si, nullptr, &set_layout));
|
|
VkPushConstantRange range{VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes};
|
|
VkPipelineLayoutCreateInfo li{.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
|
.setLayoutCount = 1, .pSetLayouts = &set_layout, .pushConstantRangeCount = 1, .pPushConstantRanges = &range};
|
|
CHECK(vkCreatePipelineLayout(device, &li, nullptr, &layout));
|
|
VkShaderModule module;
|
|
VkShaderModuleCreateInfo mi{.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
|
.codeSize = sizeof(CONTACT_SHADOW_COMP), .pCode = CONTACT_SHADOW_COMP};
|
|
CHECK(vkCreateShaderModule(device, &mi, nullptr, &module));
|
|
VkComputePipelineCreateInfo pc{.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
|
.stage = {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
|
.stage = VK_SHADER_STAGE_COMPUTE_BIT, .module = module, .pName = "main"}, .layout = layout};
|
|
for (unsigned optimized = 0; optimized < 2; ++optimized) {
|
|
VkSpecializationMapEntry entry{0, 0, sizeof optimized};
|
|
VkSpecializationInfo specialization{1, &entry, sizeof optimized, &optimized};
|
|
pc.stage.pSpecializationInfo = &specialization;
|
|
CHECK(vkCreateComputePipelines(device, {}, 1, &pc, nullptr,
|
|
optimized ? &optimized_pipeline : &pipeline));
|
|
}
|
|
vkDestroyShaderModule(device, module, nullptr);
|
|
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));
|
|
if (timestamp_bits) {
|
|
VkQueryPoolCreateInfo query{.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
|
|
.queryType = VK_QUERY_TYPE_TIMESTAMP, .queryCount = 2};
|
|
CHECK(vkCreateQueryPool(device, &query, nullptr, &timing));
|
|
}
|
|
}
|
|
void Barrier(VkImage image, VkImageLayout old_layout, VkImageLayout next) {
|
|
VkImageMemoryBarrier b{.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
.srcAccessMask = old_layout == VK_IMAGE_LAYOUT_UNDEFINED ? 0u : VK_ACCESS_MEMORY_WRITE_BIT,
|
|
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
|
.oldLayout = old_layout, .newLayout = next, .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);
|
|
}
|
|
~Test() {
|
|
vkDeviceWaitIdle(device);
|
|
for (auto i : images) {
|
|
vkDestroyImageView(device, i.view, nullptr);
|
|
vkDestroyImage(device, i.image, nullptr);
|
|
vkFreeMemory(device, i.memory, nullptr);
|
|
}
|
|
for (auto b : buffers) {
|
|
vkUnmapMemory(device, b.memory);
|
|
vkDestroyBuffer(device, b.buffer, nullptr);
|
|
vkFreeMemory(device, b.memory, nullptr);
|
|
}
|
|
vkDestroySampler(device, sampler, nullptr);
|
|
if (timing) vkDestroyQueryPool(device, timing, nullptr);
|
|
vkDestroyPipeline(device, pipeline, nullptr);
|
|
vkDestroyPipeline(device, optimized_pipeline, nullptr);
|
|
vkDestroyPipelineLayout(device, layout, nullptr);
|
|
vkDestroyDescriptorSetLayout(device, set_layout, nullptr);
|
|
vkDestroyCommandPool(device, pool, nullptr);
|
|
vkDestroyDevice(device, nullptr);
|
|
vkDestroyInstance(instance, nullptr);
|
|
}
|
|
};
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
|
const bool benchmark = argc > 1 && std::strcmp(argv[1], "--benchmark") == 0;
|
|
if (benchmark) { W = 1280; H = 720; }
|
|
Test t;
|
|
auto cb = t.MakeBuffer(1024), ub = t.MakeBuffer(1024);
|
|
auto scene = t.MakeBuffer(W * H * 4), atlas = t.MakeBuffer(Atlas * Atlas * 4);
|
|
auto pixels = t.MakeBuffer(W * H * 4), result = t.MakeBuffer(W * H * 4);
|
|
auto depth = t.MakeImage(W, H, VK_FORMAT_R32_SFLOAT);
|
|
auto map = t.MakeImage(Atlas, Atlas, VK_FORMAT_R32_SFLOAT);
|
|
auto output = t.MakeImage(W, H, VK_FORMAT_R8G8B8A8_UNORM);
|
|
auto* constants = static_cast<float*>(cb.data) + CbBase;
|
|
auto* uniforms = static_cast<float*>(ub.data) + UbBase;
|
|
for (unsigned i = 0; i < 4; ++i) constants[i * 5] = 1;
|
|
for (unsigned c = 0; c < 4; ++c) {
|
|
auto* m = constants + 16 + 16 * c;
|
|
m[0] = m[5] = 0.1f;
|
|
m[3] = 0.25f + float(c & 1) * 0.5f;
|
|
m[7] = 0.25f + float(c >> 1) * 0.5f;
|
|
m[10] = 0.5f; m[11] = 0.25f; m[15] = 1;
|
|
}
|
|
constants[80] = 0.5f; constants[81] = 0.7f; constants[82] = 0.9f;
|
|
uniforms[72] = 100; uniforms[73] = 1;
|
|
std::fill_n(static_cast<unsigned*>(pixels.data), W * H, 0x601e140au);
|
|
bool initialized = false;
|
|
bool patterned_depth = false;
|
|
double gpu_ms = 0;
|
|
const auto dispatch = [&](Push push, float view_z, bool tilted = false) {
|
|
for (unsigned y = 0; y < H; ++y)
|
|
for (unsigned x = 0; x < W; ++x)
|
|
static_cast<float*>(scene.data)[y * W + x] =
|
|
patterned_depth && (x + y) % 4 == 0 ? 1.0f :
|
|
view_z + (tilted ? 0.02f * (2 * (float(x) + 0.5f) / W - 1) : 0);
|
|
CHECK(vkResetCommandBuffer(t.cmd, 0));
|
|
VkCommandBufferBeginInfo begin{.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
|
CHECK(vkBeginCommandBuffer(t.cmd, &begin));
|
|
if (t.timing) vkCmdResetQueryPool(t.cmd, t.timing, 0, 2);
|
|
for (auto i : {depth, map, output}) t.Barrier(i.image,
|
|
initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
|
const std::array<Image, 3> ims{depth, map, output};
|
|
const std::array<Buffer, 3> src{scene, atlas, pixels};
|
|
for (unsigned i = 0; i < 3; ++i) {
|
|
VkBufferImageCopy copy{.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
|
.imageExtent = i == 1 ? VkExtent3D{Atlas, Atlas, 1} : VkExtent3D{W, H, 1}};
|
|
vkCmdCopyBufferToImage(t.cmd, src[i].buffer, ims[i].image, VK_IMAGE_LAYOUT_GENERAL, 1, ©);
|
|
t.Barrier(ims[i].image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL);
|
|
}
|
|
const VkDescriptorBufferInfo cbi{cb.buffer, 0, 1024}, ubi{ub.buffer, 0, 1024};
|
|
const VkDescriptorImageInfo d{t.sampler, depth.view, VK_IMAGE_LAYOUT_GENERAL},
|
|
m{t.sampler, map.view, VK_IMAGE_LAYOUT_GENERAL}, o{{}, output.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 = &cbi},
|
|
{.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 = &o},
|
|
{.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 = &ubi}}};
|
|
vkCmdBindPipeline(t.cmd, VK_PIPELINE_BIND_POINT_COMPUTE,
|
|
push.optimizations ? t.optimized_pipeline : t.pipeline);
|
|
t.descriptors(t.cmd, VK_PIPELINE_BIND_POINT_COMPUTE, t.layout, 0, 5, writes.data());
|
|
vkCmdPushConstants(t.cmd, t.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes, &push);
|
|
if (t.timing) vkCmdWriteTimestamp(t.cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, t.timing, 0);
|
|
const unsigned repeats = benchmark ? 16 : 1;
|
|
for (unsigned repeat = 0; repeat < repeats; ++repeat) {
|
|
if (repeat) t.Barrier(output.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL);
|
|
vkCmdDispatch(t.cmd, (W + 7) / 8, (H + 7) / 8, 1);
|
|
}
|
|
if (t.timing) vkCmdWriteTimestamp(t.cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, t.timing, 1);
|
|
t.Barrier(output.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL);
|
|
VkBufferImageCopy copy{.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .imageExtent = {W, H, 1}};
|
|
vkCmdCopyImageToBuffer(t.cmd, output.image, VK_IMAGE_LAYOUT_GENERAL, result.buffer, 1, ©);
|
|
VkMemoryBarrier host{.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
|
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_HOST_READ_BIT};
|
|
vkCmdPipelineBarrier(t.cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT,
|
|
0, 1, &host, 0, nullptr, 0, nullptr);
|
|
CHECK(vkEndCommandBuffer(t.cmd));
|
|
VkSubmitInfo submit{.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &t.cmd};
|
|
CHECK(vkQueueSubmit(t.queue, 1, &submit, {}));
|
|
CHECK(vkQueueWaitIdle(t.queue));
|
|
if (t.timing) {
|
|
std::array<std::uint64_t, 2> stamps{};
|
|
CHECK(vkGetQueryPoolResults(t.device, t.timing, 0, 2, sizeof stamps, stamps.data(),
|
|
sizeof(std::uint64_t), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT));
|
|
const auto mask = t.timestamp_bits == 64 ? ~std::uint64_t{0} : (std::uint64_t{1} << t.timestamp_bits) - 1;
|
|
gpu_ms = double((stamps[1] - stamps[0]) & mask) * t.timestamp_period / (1e6 * repeats);
|
|
}
|
|
initialized = true;
|
|
const auto* out = static_cast<unsigned*>(result.data);
|
|
float alpha = 0;
|
|
for (unsigned i = 0; i < W * H; ++i) {
|
|
assert((out[i] & 0x00ffffff) == 0x001e140a);
|
|
alpha += float(out[i] >> 24) / 255;
|
|
}
|
|
return alpha / (W * H);
|
|
};
|
|
const auto run = [&](Push push, float view_z, bool tilted = false) {
|
|
push.optimizations = 0;
|
|
const float alpha = dispatch(push, view_z, tilted);
|
|
const auto* data = static_cast<unsigned*>(result.data);
|
|
std::vector<unsigned> baseline(data, data + W * H);
|
|
push.optimizations = 1;
|
|
dispatch(push, view_z, tilted);
|
|
assert(std::equal(baseline.begin(), baseline.end(), static_cast<unsigned*>(result.data)));
|
|
return alpha;
|
|
};
|
|
auto* depths = static_cast<float*>(atlas.data);
|
|
Push p;
|
|
if (benchmark) {
|
|
assert(t.timing);
|
|
for (unsigned y = 0; y < Atlas; ++y)
|
|
for (unsigned x = 0; x < Atlas; ++x)
|
|
depths[y * Atlas + x] = x < 16 ? 0.2f : 1.0f;
|
|
for (unsigned scenario = 0; scenario < 4; ++scenario) {
|
|
const unsigned pcss = scenario & 1;
|
|
p.pcss = pcss; p.contact = 1; p.blend = 0.1f;
|
|
p.light = 0.5f;
|
|
uniforms[72] = scenario < 2 ? 100 : 0;
|
|
run(p, 0.475f, true);
|
|
std::array<std::vector<double>, 2> samples;
|
|
for (unsigned trial = 0; trial < 24; ++trial) {
|
|
for (unsigned order = 0; order < 2; ++order) {
|
|
p.optimizations = (order + trial) % 2;
|
|
dispatch(p, 0.475f, true);
|
|
if (trial >= 4) samples[p.optimizations].push_back(gpu_ms);
|
|
}
|
|
}
|
|
for (auto& s : samples) std::sort(s.begin(), s.end());
|
|
std::printf("1280x720 %s + contact (%s), median GPU ms: original %.4f, optimized %.4f\n",
|
|
pcss ? "PCSS" : "PCF", scenario < 2 ? "visible" : "fully faded",
|
|
samples[0][samples[0].size()/2], samples[1][samples[1].size()/2]);
|
|
}
|
|
return 0;
|
|
}
|
|
std::fill_n(depths, Atlas * Atlas, 1.0f);
|
|
assert(run(p, 0.4f) > 0.999f);
|
|
std::fill_n(depths, Atlas * Atlas, 0.0f);
|
|
assert(run(p, 0.4f) < 0.001f);
|
|
p.filter = 0;
|
|
assert(std::abs(run(p, 0.4f) - 96.0f / 255) < 0.001f);
|
|
p.filter = 1;
|
|
assert(std::abs(run(p, 1.0f) - 96.0f / 255) < 0.001f);
|
|
for (unsigned c = 0; c < 4; ++c) {
|
|
const float z = c == 0 ? 0.4f : c == 1 ? 0.6f : c == 2 ? 0.8f : 0.95f;
|
|
auto* matrix = constants + 16 + 16 * c;
|
|
const float saved_u = matrix[3];
|
|
matrix[0] = 0;
|
|
matrix[3] = 0.5f * float((c & 1) + 1) - 0.001f;
|
|
for (unsigned y = 0; y < Atlas; ++y)
|
|
for (unsigned x = 0; x < Atlas; ++x)
|
|
depths[y * Atlas + x] = (x / 32 + 2 * (y / 32)) == c ? 0.0f : 1.0f;
|
|
|
|
matrix[0] = 0.0005f;
|
|
p.pcss = 0;
|
|
assert(run(p, z) < 0.001f);
|
|
p.pcss = 1; p.maximum = 10;
|
|
assert(run(p, z) < 0.001f);
|
|
matrix[0] = 0.1f; matrix[3] = saved_u;
|
|
}
|
|
p.pcss = 0; p.maximum = 0.3f; p.blend = 0.1f;
|
|
for (unsigned y = 0; y < Atlas; ++y)
|
|
for (unsigned x = 0; x < Atlas; ++x)
|
|
depths[y * Atlas + x] = x < 32 && y < 32 ? 0.0f : 1.0f;
|
|
assert(run(p, 0.45f) < 0.001f);
|
|
assert(std::abs(run(p, 0.475f) - 0.5f) < 0.01f);
|
|
assert(run(p, 0.5f) > 0.999f && run(p, 0.501f) > 0.999f);
|
|
p.blend = 0;
|
|
assert(run(p, 0.475f) < 0.001f);
|
|
for (unsigned y = 0; y < Atlas; ++y)
|
|
for (unsigned x = 0; x < Atlas; ++x)
|
|
depths[y * Atlas + x] = 0.45f + 0.1f * ((float(x) + 0.5f) / Atlas - 0.25f);
|
|
assert(run(p, 0.4f, true) > 0.999f);
|
|
p.pcss = 1;
|
|
assert(run(p, 0.4f, true) > 0.999f);
|
|
for (unsigned y = 0; y < Atlas; ++y)
|
|
for (unsigned x = 0; x < Atlas; ++x)
|
|
depths[y * Atlas + x] -= 0.1f;
|
|
assert(run(p, 0.4f, true) < 0.001f);
|
|
p.pcss = 0;
|
|
for (unsigned y = 0; y < Atlas; ++y)
|
|
for (unsigned x = 0; x < Atlas; ++x)
|
|
depths[y * Atlas + x] = x < 16 ? 0.0f : 1.0f;
|
|
const float edge = run(p, 0.4f);
|
|
assert(edge > 0.4f && edge < 0.6f);
|
|
unsigned fractional = 0;
|
|
for (unsigned i = 0; i < W * H; ++i) {
|
|
const unsigned a = static_cast<unsigned*>(result.data)[i] >> 24;
|
|
fractional += a > 0 && a < 255;
|
|
}
|
|
std::printf("Regular PCF fractional edge pixels: %u\n", fractional);
|
|
assert(fractional >= 2 * H && fractional <= 5 * H);
|
|
|
|
p.pcss = 1; p.bias = 0; p.light = 1; p.maximum = 2;
|
|
const auto soft_pixels = [&] {
|
|
unsigned count = 0;
|
|
for (unsigned i = 0; i < W * H; ++i) {
|
|
const unsigned a = static_cast<unsigned*>(result.data)[i] >> 24;
|
|
count += a > 12 && a < 243;
|
|
}
|
|
return count;
|
|
};
|
|
const auto edge_at = [&](float blocker) {
|
|
for (unsigned y = 0; y < Atlas; ++y)
|
|
for (unsigned x = 0; x < Atlas; ++x)
|
|
depths[y * Atlas + x] = x < 16 ? blocker : 1.0f;
|
|
run(p, 0.4f);
|
|
return soft_pixels();
|
|
};
|
|
const unsigned contact_width = edge_at(0.449f);
|
|
const unsigned middle_width = edge_at(0.2f);
|
|
const unsigned far_width = edge_at(0.0f);
|
|
std::printf("PCSS edge widths (pixels): contact %u, middle %u, far %u\n",
|
|
contact_width, middle_width, far_width);
|
|
assert(contact_width >= H && middle_width > contact_width && far_width > middle_width);
|
|
|
|
auto* first = constants + 16;
|
|
first[0] = first[5] = 0.0001f;
|
|
first[3] = first[7] = 16.5f / Atlas;
|
|
std::fill_n(depths, Atlas * Atlas, 1.0f);
|
|
depths[16 * Atlas + 16] = 0.449f;
|
|
const float thin_visibility = run(p, 0.4f);
|
|
std::printf("PCSS single-texel caster visibility: %.4f\n", thin_visibility);
|
|
assert(thin_visibility < 0.5f);
|
|
first[0] = first[5] = 0.1f;
|
|
first[3] = first[7] = 0.25f;
|
|
|
|
edge_at(0.2f);
|
|
std::vector<unsigned> previous(static_cast<unsigned*>(result.data),
|
|
static_cast<unsigned*>(result.data) + W * H);
|
|
p.frame = 37;
|
|
run(p, 0.4f);
|
|
assert(std::equal(previous.begin(), previous.end(), static_cast<unsigned*>(result.data)));
|
|
uniforms[72] = 0;
|
|
assert(run(p, 0.4f) > 0.999f);
|
|
p.contact = 1;
|
|
run(p, 0.4f);
|
|
first[3] = 2;
|
|
assert(std::abs(run(p, 0.4f) - 96.0f / 255) < 0.001f);
|
|
first[3] = 0.25f;
|
|
|
|
constants[0] = 0.8f; constants[5] = -0.7f;
|
|
constants[3] = 0.03f; constants[7] = -0.02f;
|
|
constants[10] = 0.9f; constants[14] = 0.07f;
|
|
uniforms[72] = 100;
|
|
for (unsigned mode : {0u, 1u}) {
|
|
p.pcss = mode;
|
|
p.blend = 0.1f;
|
|
for (float z : {0.4f, 0.54f, 0.75f, 0.96f})
|
|
run(p, z, true);
|
|
for (float z : {0.0f, 1.0f, std::numeric_limits<float>::quiet_NaN()})
|
|
run(p, z);
|
|
patterned_depth = true;
|
|
for (float fade_end : {0.0f, 1.0f, 100.0f}) {
|
|
uniforms[72] = fade_end;
|
|
run(p, 0.54f, true);
|
|
}
|
|
patterned_depth = false;
|
|
}
|
|
std::puts("Directional shadows: PASS (four atlas tiles, PCF/PCSS edges, lit/shadow, "
|
|
"cascade blending, sloped receivers, fractional comparisons, stock/sky fallback, fade, "
|
|
"RGB preservation, odd sizes, buffer offsets, contact hardening, thin caster, frame stability)");
|
|
}
|