mirror of
https://github.com/Awakenchan/LanternEngine.git
synced 2026-11-11 01:11:43 -10:00
366 lines
19 KiB
C++
366 lines
19 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
#include <vulkan/vulkan.h>
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <mutex>
|
|
#include <thread>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
struct Entry {
|
|
std::string name, stage, spv, partner;
|
|
std::vector<VkDescriptorSetLayoutBinding> bindings;
|
|
std::vector<std::pair<uint32_t, VkFormat>> vertex_inputs, color_outputs;
|
|
};
|
|
|
|
std::vector<std::string> Split(const std::string& s, char sep) {
|
|
std::vector<std::string> out(1);
|
|
for (const char c : s) {
|
|
if (c == sep) {
|
|
out.emplace_back();
|
|
} else {
|
|
out.back() += c;
|
|
}
|
|
}
|
|
if (out.size() == 1 && out[0].empty()) out.clear();
|
|
return out;
|
|
}
|
|
|
|
std::vector<uint32_t> ReadSpv(const std::string& path, uint32_t nonce) {
|
|
std::ifstream in(path, std::ios::binary);
|
|
std::vector<char> bytes{std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>()};
|
|
std::vector<uint32_t> words(bytes.size() / 4);
|
|
std::memcpy(words.data(), bytes.data(), words.size() * 4);
|
|
if (words.size() > 5) {
|
|
words[3] += nonce;
|
|
}
|
|
return words;
|
|
}
|
|
|
|
VkDescriptorType Kind(const std::string& k) {
|
|
if (k == "ub") return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
if (k == "sb") return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
if (k == "si") return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
|
|
if (k == "st") return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
|
if (k == "sa") return VK_DESCRIPTOR_TYPE_SAMPLER;
|
|
if (k == "ut") return VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
|
|
if (k == "stb") return VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
|
|
return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
}
|
|
|
|
std::vector<Entry> ReadManifest(const char* path) {
|
|
std::vector<Entry> entries;
|
|
std::ifstream in(path);
|
|
for (std::string line; std::getline(in, line);) {
|
|
if (!line.empty() && line.back() == '\r') line.pop_back();
|
|
const auto f = Split(line, '\t');
|
|
if (f.size() < 7) continue;
|
|
Entry e{f[0], f[1], f[2], f[3]};
|
|
for (const auto& b : Split(f[4], ';')) {
|
|
const auto p = Split(b, ':');
|
|
if (p.size() != 3) continue;
|
|
e.bindings.push_back({uint32_t(std::stoul(p[0])), Kind(p[1]), uint32_t(std::stoul(p[2])),
|
|
VK_SHADER_STAGE_ALL, nullptr});
|
|
}
|
|
for (int list = 0; list < 2; ++list) {
|
|
for (const auto& v : Split(f[5 + list], ';')) {
|
|
const auto p = Split(v, ':');
|
|
if (p.size() != 2) continue;
|
|
(list ? e.color_outputs : e.vertex_inputs)
|
|
.push_back({uint32_t(std::stoul(p[0])), VkFormat(std::stoul(p[1]))});
|
|
}
|
|
}
|
|
entries.push_back(std::move(e));
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
#define CHECK(x) \
|
|
do { \
|
|
const VkResult r_ = (x); \
|
|
if (r_ != VK_SUCCESS) { \
|
|
std::fprintf(stderr, "%s failed: %d\n", #x, int(r_)); \
|
|
std::exit(1); \
|
|
} \
|
|
} while (0)
|
|
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
if (argc < 3) {
|
|
std::fprintf(stderr, "usage: bb-shader-gpu-stats <manifest> <report.csv> [rounds] [threads]\n");
|
|
return 2;
|
|
}
|
|
const auto entries = ReadManifest(argv[1]);
|
|
const int rounds = argc >= 4 ? std::max(1, std::atoi(argv[3])) : 1;
|
|
const int threads = argc >= 5 ? std::max(1, std::atoi(argv[4])) : 1;
|
|
|
|
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO};
|
|
app.pApplicationName = "bb-shader-gpu-stats";
|
|
app.apiVersion = VK_API_VERSION_1_3;
|
|
VkInstanceCreateInfo ici{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
|
ici.pApplicationInfo = &app;
|
|
VkInstance instance;
|
|
CHECK(vkCreateInstance(&ici, nullptr, &instance));
|
|
uint32_t count = 0;
|
|
vkEnumeratePhysicalDevices(instance, &count, nullptr);
|
|
std::vector<VkPhysicalDevice> gpus(count);
|
|
vkEnumeratePhysicalDevices(instance, &count, gpus.data());
|
|
VkPhysicalDevice gpu = VK_NULL_HANDLE;
|
|
VkPhysicalDeviceProperties props{};
|
|
for (auto g : gpus) {
|
|
vkGetPhysicalDeviceProperties(g, &props);
|
|
if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
|
|
gpu = g;
|
|
break;
|
|
}
|
|
}
|
|
if (!gpu && !gpus.empty()) gpu = gpus[0];
|
|
vkGetPhysicalDeviceProperties(gpu, &props);
|
|
|
|
VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR bary{
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_BARYCENTRIC_FEATURES_KHR};
|
|
VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR exec{
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR, &bary};
|
|
VkPhysicalDeviceVulkan13Features f13{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, &exec};
|
|
VkPhysicalDeviceVulkan12Features f12{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, &f13};
|
|
VkPhysicalDeviceVulkan11Features f11{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, &f12};
|
|
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &f11};
|
|
vkGetPhysicalDeviceFeatures2(gpu, &features);
|
|
features.features.robustBufferAccess = VK_FALSE;
|
|
|
|
uint32_t ext_count = 0;
|
|
vkEnumerateDeviceExtensionProperties(gpu, nullptr, &ext_count, nullptr);
|
|
std::vector<VkExtensionProperties> offered(ext_count);
|
|
vkEnumerateDeviceExtensionProperties(gpu, nullptr, &ext_count, offered.data());
|
|
std::vector<const char*> extensions;
|
|
for (const char* wanted : {VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME,
|
|
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
|
|
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME,
|
|
VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME}) {
|
|
for (const auto& o : offered) {
|
|
if (std::strcmp(o.extensionName, wanted) == 0) extensions.push_back(wanted);
|
|
}
|
|
}
|
|
float priority = 1.0f;
|
|
VkDeviceQueueCreateInfo qci{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
|
|
qci.queueCount = 1;
|
|
qci.pQueuePriorities = &priority;
|
|
VkDeviceCreateInfo dci{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, &features};
|
|
dci.queueCreateInfoCount = 1;
|
|
dci.pQueueCreateInfos = &qci;
|
|
dci.enabledExtensionCount = uint32_t(extensions.size());
|
|
dci.ppEnabledExtensionNames = extensions.data();
|
|
VkDevice device;
|
|
CHECK(vkCreateDevice(gpu, &dci, nullptr, &device));
|
|
const auto get_props = reinterpret_cast<PFN_vkGetPipelineExecutablePropertiesKHR>(
|
|
vkGetDeviceProcAddr(device, "vkGetPipelineExecutablePropertiesKHR"));
|
|
const auto get_stats = reinterpret_cast<PFN_vkGetPipelineExecutableStatisticsKHR>(
|
|
vkGetDeviceProcAddr(device, "vkGetPipelineExecutableStatisticsKHR"));
|
|
|
|
std::FILE* out = std::fopen(argv[2], "w");
|
|
std::fprintf(out, "name,stage,round,create_ms,executable,statistic,value\n");
|
|
const uint32_t nonce = uint32_t(std::chrono::steady_clock::now().time_since_epoch().count() & 0xFFFF) + 1;
|
|
size_t built = 0, failed = 0;
|
|
double total_ms = 0, wall_ms = 0;
|
|
std::mutex out_mutex;
|
|
for (int round = 0; round < rounds; ++round) {
|
|
const auto build = [&](const Entry& e) {
|
|
const auto make_module = [&](const std::string& path) {
|
|
const auto words = ReadSpv(path, nonce + uint32_t(round) * 0x10000);
|
|
VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
|
smci.codeSize = words.size() * 4;
|
|
smci.pCode = words.data();
|
|
VkShaderModule module = VK_NULL_HANDLE;
|
|
vkCreateShaderModule(device, &smci, nullptr, &module);
|
|
return module;
|
|
};
|
|
VkDescriptorSetLayoutCreateInfo dslci{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
|
|
dslci.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR;
|
|
dslci.bindingCount = uint32_t(e.bindings.size());
|
|
dslci.pBindings = e.bindings.data();
|
|
VkDescriptorSetLayout set_layout;
|
|
if (vkCreateDescriptorSetLayout(device, &dslci, nullptr, &set_layout) != VK_SUCCESS) {
|
|
dslci.flags = 0;
|
|
CHECK(vkCreateDescriptorSetLayout(device, &dslci, nullptr, &set_layout));
|
|
}
|
|
VkPushConstantRange push{VK_SHADER_STAGE_ALL, 0,
|
|
std::min<uint32_t>(256, props.limits.maxPushConstantsSize)};
|
|
VkPipelineLayoutCreateInfo plci{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
|
|
VkDescriptorSetLayoutBinding motion_binding{0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr};
|
|
VkDescriptorSetLayoutCreateInfo motion_info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
|
|
motion_info.bindingCount = 1;
|
|
motion_info.pBindings = &motion_binding;
|
|
VkDescriptorSetLayout motion_layout;
|
|
CHECK(vkCreateDescriptorSetLayout(device, &motion_info, nullptr, &motion_layout));
|
|
const VkDescriptorSetLayout set_layouts[2] = {set_layout, motion_layout};
|
|
plci.setLayoutCount = 2;
|
|
plci.pSetLayouts = set_layouts;
|
|
plci.pushConstantRangeCount = 1;
|
|
plci.pPushConstantRanges = &push;
|
|
VkPipelineLayout layout;
|
|
CHECK(vkCreatePipelineLayout(device, &plci, nullptr, &layout));
|
|
|
|
const VkShaderModule module = make_module(e.spv);
|
|
const VkShaderModule partner = e.partner == "-" ? VK_NULL_HANDLE : make_module(e.partner);
|
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
|
VkResult result = VK_ERROR_UNKNOWN;
|
|
const auto start = std::chrono::steady_clock::now();
|
|
if (e.stage == "comp") {
|
|
VkComputePipelineCreateInfo cpci{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
|
|
cpci.flags = VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
|
|
cpci.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, 0,
|
|
VK_SHADER_STAGE_COMPUTE_BIT, module, "main", nullptr};
|
|
cpci.layout = layout;
|
|
if (module) result = vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &cpci, nullptr, &pipeline);
|
|
} else {
|
|
const bool is_vs = e.stage == "vert";
|
|
VkPipelineShaderStageCreateInfo stages[2] = {
|
|
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, 0,
|
|
VK_SHADER_STAGE_VERTEX_BIT, is_vs ? module : partner, "main", nullptr},
|
|
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, 0,
|
|
VK_SHADER_STAGE_FRAGMENT_BIT, is_vs ? partner : module, "main", nullptr}};
|
|
std::vector<VkVertexInputAttributeDescription> attribs;
|
|
for (const auto& [location, format] : e.vertex_inputs) {
|
|
attribs.push_back({location, 0, format, 0});
|
|
}
|
|
VkVertexInputBindingDescription vbind{0, 16, VK_VERTEX_INPUT_RATE_VERTEX};
|
|
VkPipelineVertexInputStateCreateInfo vi{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
|
|
vi.vertexBindingDescriptionCount = attribs.empty() ? 0 : 1;
|
|
vi.pVertexBindingDescriptions = &vbind;
|
|
vi.vertexAttributeDescriptionCount = uint32_t(attribs.size());
|
|
vi.pVertexAttributeDescriptions = attribs.data();
|
|
VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
|
ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
|
VkPipelineViewportStateCreateInfo vp{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
|
|
vp.viewportCount = 1;
|
|
vp.scissorCount = 1;
|
|
VkPipelineRasterizationStateCreateInfo rs{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
|
|
rs.lineWidth = 1.0f;
|
|
VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
|
ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
|
VkPipelineDepthStencilStateCreateInfo ds{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
|
ds.depthTestEnable = VK_TRUE;
|
|
ds.depthWriteEnable = VK_TRUE;
|
|
ds.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
|
|
uint32_t max_location = 0;
|
|
for (const auto& [location, format] : e.color_outputs) {
|
|
max_location = std::max(max_location, location + 1);
|
|
}
|
|
std::vector<VkFormat> formats(max_location, VK_FORMAT_UNDEFINED);
|
|
for (const auto& [location, format] : e.color_outputs) {
|
|
formats[location] = format;
|
|
}
|
|
std::vector<VkPipelineColorBlendAttachmentState> blend(max_location);
|
|
for (auto& b : blend) {
|
|
b.colorWriteMask = 0xF;
|
|
}
|
|
VkPipelineColorBlendStateCreateInfo cb{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
|
|
cb.attachmentCount = max_location;
|
|
cb.pAttachments = blend.data();
|
|
const VkDynamicState dyn_states[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
|
|
VkPipelineDynamicStateCreateInfo dyn{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
|
|
dyn.dynamicStateCount = 2;
|
|
dyn.pDynamicStates = dyn_states;
|
|
VkPipelineRenderingCreateInfo rendering{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO};
|
|
rendering.colorAttachmentCount = max_location;
|
|
rendering.pColorAttachmentFormats = formats.data();
|
|
rendering.depthAttachmentFormat = VK_FORMAT_D32_SFLOAT_S8_UINT;
|
|
rendering.stencilAttachmentFormat = VK_FORMAT_D32_SFLOAT_S8_UINT;
|
|
VkGraphicsPipelineCreateInfo gpci{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &rendering};
|
|
gpci.flags = VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
|
|
gpci.stageCount = 2;
|
|
gpci.pStages = stages;
|
|
gpci.pVertexInputState = &vi;
|
|
gpci.pInputAssemblyState = &ia;
|
|
gpci.pViewportState = &vp;
|
|
gpci.pRasterizationState = &rs;
|
|
gpci.pMultisampleState = &ms;
|
|
gpci.pDepthStencilState = &ds;
|
|
gpci.pColorBlendState = &cb;
|
|
gpci.pDynamicState = &dyn;
|
|
gpci.layout = layout;
|
|
if (module && partner) result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &gpci, nullptr, &pipeline);
|
|
}
|
|
const double ms_taken =
|
|
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
|
std::scoped_lock lock{out_mutex};
|
|
if (result != VK_SUCCESS) {
|
|
std::fprintf(out, "%s,%s,%d,,,failed,%d\n", e.name.c_str(), e.stage.c_str(), round, int(result));
|
|
++failed;
|
|
} else {
|
|
++built;
|
|
total_ms += ms_taken;
|
|
VkPipelineInfoKHR pinfo{VK_STRUCTURE_TYPE_PIPELINE_INFO_KHR};
|
|
pinfo.pipeline = pipeline;
|
|
uint32_t n = 0;
|
|
if (get_props) get_props(device, &pinfo, &n, nullptr);
|
|
std::vector<VkPipelineExecutablePropertiesKHR> execs(
|
|
n, {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_PROPERTIES_KHR});
|
|
if (get_props) get_props(device, &pinfo, &n, execs.data());
|
|
for (uint32_t i = 0; i < n; ++i) {
|
|
VkPipelineExecutableInfoKHR einfo{VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR};
|
|
einfo.pipeline = pipeline;
|
|
einfo.executableIndex = i;
|
|
uint32_t sn = 0;
|
|
get_stats(device, &einfo, &sn, nullptr);
|
|
std::vector<VkPipelineExecutableStatisticKHR> stats(
|
|
sn, {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_STATISTIC_KHR});
|
|
get_stats(device, &einfo, &sn, stats.data());
|
|
for (const auto& s : stats) {
|
|
double value = 0;
|
|
switch (s.format) {
|
|
case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_BOOL32_KHR: value = s.value.b32; break;
|
|
case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_INT64_KHR: value = double(s.value.i64); break;
|
|
case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_UINT64_KHR: value = double(s.value.u64); break;
|
|
case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_FLOAT64_KHR: value = s.value.f64; break;
|
|
default: break;
|
|
}
|
|
std::fprintf(out, "%s,%s,%d,%.3f,%s,%s,%g\n", e.name.c_str(), e.stage.c_str(), round,
|
|
ms_taken, execs[i].name, s.name, value);
|
|
}
|
|
}
|
|
vkDestroyPipeline(device, pipeline, nullptr);
|
|
}
|
|
if (module) vkDestroyShaderModule(device, module, nullptr);
|
|
if (partner) vkDestroyShaderModule(device, partner, nullptr);
|
|
vkDestroyPipelineLayout(device, layout, nullptr);
|
|
vkDestroyDescriptorSetLayout(device, motion_layout, nullptr);
|
|
vkDestroyDescriptorSetLayout(device, set_layout, nullptr);
|
|
};
|
|
std::atomic<size_t> next{0};
|
|
const auto worker = [&] {
|
|
for (size_t k; (k = next.fetch_add(1)) < entries.size();) {
|
|
build(entries[k]);
|
|
}
|
|
};
|
|
const auto round_start = std::chrono::steady_clock::now();
|
|
std::vector<std::thread> pool;
|
|
for (int t = 1; t < threads; ++t) {
|
|
pool.emplace_back(worker);
|
|
}
|
|
worker();
|
|
for (auto& thread : pool) {
|
|
thread.join();
|
|
}
|
|
wall_ms += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - round_start).count();
|
|
}
|
|
std::fclose(out);
|
|
std::printf("%s (driver %u.%u): %zu pipelines built in %.1f ms on %d threads (%.1f ms of driver "
|
|
"time), %zu failed\n",
|
|
props.deviceName, props.driverVersion >> 22, (props.driverVersion >> 14) & 0xFF, built,
|
|
wall_ms, threads, total_ms, failed);
|
|
vkDestroyDevice(device, nullptr);
|
|
vkDestroyInstance(instance, nullptr);
|
|
return failed ? 1 : 0;
|
|
}
|