mirror of
https://github.com/Awakenchan/LanternEngine.git
synced 2026-11-11 01:11:43 -10:00
637 lines
28 KiB
C++
637 lines
28 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
// bbport: FSR 4 benchmark outside the game. Runs the v07 INT8 provider (the same assets and
|
|
// provider as vk_fsr4.cpp) on synthetic inputs and prints GPU time per pass (BB_FSR4_PROFILE)
|
|
// and, with --stats, the driver's statistics of every pass (BB_FSR4_STATS).
|
|
//
|
|
// fsr4-bench [render WxH] [output WxH] [preset 0-4] [frames] [--stats] [--fsr411]
|
|
// --fsr411: FSR 4.1.1 replay (fsr411.cpp, assets in BB_FSR411_DIR or fsr4_411) instead of v07;
|
|
// its frames match tools/fsr4cap (jitter phase, reset on the first frame).
|
|
// defaults: 2260x1272 3840x2160 2 (balanced) 900
|
|
// BENCH_NOISE=1: pseudo-random inputs (a fixed seed); BENCH_DUMP=<file>: the output after the
|
|
// last frame, raw RGBA16F, for comparing shader variants.
|
|
// Assets: BB_FSR4_DIR or fsr4_shaders in the working directory.
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <vulkan/vulkan.h>
|
|
|
|
#include "ffx_vk_fsr4_v07.h"
|
|
#include "ffx_vk_fsr4_v07_assets.h"
|
|
#include "fsr411.h"
|
|
|
|
namespace {
|
|
|
|
#define CHECK(call) \
|
|
do { \
|
|
const VkResult r_ = (call); \
|
|
if (r_ != VK_SUCCESS) { \
|
|
std::fprintf(stderr, "%s failed: %d\n", #call, int(r_)); \
|
|
std::exit(1); \
|
|
} \
|
|
} while (0)
|
|
|
|
bool ReadFile(const std::string& path, std::vector<unsigned char>& data) {
|
|
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
|
if (!file) {
|
|
return false;
|
|
}
|
|
data.resize(size_t(file.tellg()));
|
|
file.seekg(0);
|
|
return bool(file.read(reinterpret_cast<char*>(data.data()), std::streamsize(data.size())));
|
|
}
|
|
|
|
struct Gpu {
|
|
VkInstance instance{};
|
|
VkPhysicalDevice physical{};
|
|
VkDevice device{};
|
|
VkQueue queue{};
|
|
uint32_t family = 0;
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
Fsr411::Features fsr411; ///< what the device was created with for FSR 4.1.1's variants
|
|
};
|
|
|
|
uint32_t MemoryType(const Gpu& gpu, uint32_t bits, VkMemoryPropertyFlags flags) {
|
|
for (uint32_t i = 0; i < gpu.memory.memoryTypeCount; ++i) {
|
|
if ((bits & (1u << i)) && (gpu.memory.memoryTypes[i].propertyFlags & flags) == flags) {
|
|
return i;
|
|
}
|
|
}
|
|
std::fprintf(stderr, "no memory type\n");
|
|
std::exit(1);
|
|
}
|
|
|
|
Gpu CreateGpu(bool stats) {
|
|
Gpu gpu;
|
|
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO};
|
|
app.pApplicationName = "fsr4-bench";
|
|
app.apiVersion = VK_API_VERSION_1_3;
|
|
VkInstanceCreateInfo ici{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
|
ici.pApplicationInfo = &app;
|
|
CHECK(vkCreateInstance(&ici, nullptr, &gpu.instance));
|
|
uint32_t count = 0;
|
|
CHECK(vkEnumeratePhysicalDevices(gpu.instance, &count, nullptr));
|
|
std::vector<VkPhysicalDevice> devices(count);
|
|
CHECK(vkEnumeratePhysicalDevices(gpu.instance, &count, devices.data()));
|
|
gpu.physical = devices.at(0);
|
|
for (const auto device : devices) {
|
|
VkPhysicalDeviceProperties props;
|
|
vkGetPhysicalDeviceProperties(device, &props);
|
|
if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
|
|
gpu.physical = device;
|
|
break;
|
|
}
|
|
}
|
|
VkPhysicalDeviceProperties props;
|
|
vkGetPhysicalDeviceProperties(gpu.physical, &props);
|
|
std::printf("GPU: %s\n", props.deviceName);
|
|
vkGetPhysicalDeviceMemoryProperties(gpu.physical, &gpu.memory);
|
|
|
|
uint32_t families = 0;
|
|
vkGetPhysicalDeviceQueueFamilyProperties(gpu.physical, &families, nullptr);
|
|
std::vector<VkQueueFamilyProperties> family_props(families);
|
|
vkGetPhysicalDeviceQueueFamilyProperties(gpu.physical, &families, family_props.data());
|
|
while (gpu.family < families && !(family_props[gpu.family].queueFlags & VK_QUEUE_COMPUTE_BIT)) {
|
|
++gpu.family;
|
|
}
|
|
|
|
// Everything the device supports, as the game's device enables what FSR 4 needs.
|
|
VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR executable{
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR};
|
|
VkPhysicalDeviceComputeShaderDerivativesFeaturesKHR derivatives{
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COMPUTE_SHADER_DERIVATIVES_FEATURES_KHR};
|
|
// Cooperative matrix (WMMA) for experimental model passes, when the device has it.
|
|
VkPhysicalDeviceCooperativeMatrixFeaturesKHR coopmat{
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR};
|
|
// FSR 4.1.1's FP8 variant: FP8 cooperative matrices (RDNA4).
|
|
VkPhysicalDeviceShaderFloat8FeaturesEXT float8{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT8_FEATURES_EXT};
|
|
// FSR 4.1.1 passes use mixed float dot products (dot2 of halves into float), as vkd3d-proton.
|
|
VkPhysicalDeviceShaderMixedFloatDotProductFeaturesVALVE mixed_dot{
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_MIXED_FLOAT_DOT_PRODUCT_FEATURES_VALVE};
|
|
VkPhysicalDeviceVulkan13Features f13{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES};
|
|
VkPhysicalDeviceVulkan12Features f12{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES};
|
|
VkPhysicalDeviceVulkan11Features f11{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES};
|
|
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
|
|
features.pNext = &f11;
|
|
f11.pNext = &f12;
|
|
f12.pNext = &f13;
|
|
f13.pNext = &derivatives;
|
|
derivatives.pNext = &coopmat;
|
|
coopmat.pNext = &float8;
|
|
float8.pNext = &mixed_dot;
|
|
if (stats) {
|
|
mixed_dot.pNext = &executable;
|
|
}
|
|
vkGetPhysicalDeviceFeatures2(gpu.physical, &features);
|
|
features.features.robustBufferAccess = VK_FALSE; // as the game: no robustness cost
|
|
f13.robustImageAccess = VK_FALSE;
|
|
std::vector<const char*> extensions{VK_KHR_COMPUTE_SHADER_DERIVATIVES_EXTENSION_NAME};
|
|
extensions.push_back(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
|
if (mixed_dot.shaderMixedFloatDotProductFloat16AccFloat32) {
|
|
extensions.push_back(VK_VALVE_SHADER_MIXED_FLOAT_DOT_PRODUCT_EXTENSION_NAME);
|
|
}
|
|
if (coopmat.cooperativeMatrix) {
|
|
extensions.push_back(VK_KHR_COOPERATIVE_MATRIX_EXTENSION_NAME);
|
|
}
|
|
// RADV fills the FP8 features in also where it does not offer the extension (RDNA3).
|
|
uint32_t available_count = 0;
|
|
vkEnumerateDeviceExtensionProperties(gpu.physical, nullptr, &available_count, nullptr);
|
|
std::vector<VkExtensionProperties> available(available_count);
|
|
vkEnumerateDeviceExtensionProperties(gpu.physical, nullptr, &available_count, available.data());
|
|
const bool has_float8 = std::any_of(available.begin(), available.end(), [](const auto& e) {
|
|
return std::strcmp(e.extensionName, VK_EXT_SHADER_FLOAT8_EXTENSION_NAME) == 0;
|
|
});
|
|
if (!has_float8) {
|
|
float8.shaderFloat8 = float8.shaderFloat8CooperativeMatrix = VK_FALSE;
|
|
}
|
|
if (float8.shaderFloat8CooperativeMatrix) {
|
|
extensions.push_back(VK_EXT_SHADER_FLOAT8_EXTENSION_NAME);
|
|
} else {
|
|
coopmat.pNext = float8.pNext;
|
|
}
|
|
// The matrix passes run as wave32 in full subgroups, with the Vulkan memory model.
|
|
const bool matrices = coopmat.cooperativeMatrix && f12.vulkanMemoryModel && f13.subgroupSizeControl &&
|
|
f13.computeFullSubgroups;
|
|
gpu.fsr411.fp8_matrices = matrices && float8.shaderFloat8CooperativeMatrix;
|
|
gpu.fsr411.fp16_matrices = matrices && f12.shaderFloat16;
|
|
if (stats) {
|
|
extensions.push_back(VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME);
|
|
}
|
|
const float priority = 1.0f;
|
|
VkDeviceQueueCreateInfo qci{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
|
|
qci.queueFamilyIndex = gpu.family;
|
|
qci.queueCount = 1;
|
|
qci.pQueuePriorities = &priority;
|
|
VkDeviceCreateInfo dci{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
|
|
dci.pNext = &features;
|
|
dci.queueCreateInfoCount = 1;
|
|
dci.pQueueCreateInfos = &qci;
|
|
dci.enabledExtensionCount = uint32_t(extensions.size());
|
|
dci.ppEnabledExtensionNames = extensions.data();
|
|
CHECK(vkCreateDevice(gpu.physical, &dci, nullptr, &gpu.device));
|
|
vkGetDeviceQueue(gpu.device, gpu.family, 0, &gpu.queue);
|
|
return gpu;
|
|
}
|
|
|
|
struct Buffer {
|
|
VkBuffer buffer{};
|
|
VkDeviceMemory memory{};
|
|
void* data = nullptr;
|
|
};
|
|
|
|
Buffer CreateHostBuffer(const Gpu& gpu, VkDeviceSize size, VkBufferUsageFlags usage) {
|
|
Buffer b;
|
|
VkBufferCreateInfo ci{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
|
ci.size = size;
|
|
ci.usage = usage;
|
|
CHECK(vkCreateBuffer(gpu.device, &ci, nullptr, &b.buffer));
|
|
VkMemoryRequirements req;
|
|
vkGetBufferMemoryRequirements(gpu.device, b.buffer, &req);
|
|
VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
|
ai.allocationSize = req.size;
|
|
ai.memoryTypeIndex = MemoryType(gpu, req.memoryTypeBits,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
|
CHECK(vkAllocateMemory(gpu.device, &ai, nullptr, &b.memory));
|
|
CHECK(vkBindBufferMemory(gpu.device, b.buffer, b.memory, 0));
|
|
CHECK(vkMapMemory(gpu.device, b.memory, 0, VK_WHOLE_SIZE, 0, &b.data));
|
|
return b;
|
|
}
|
|
|
|
uint16_t Half(float f) {
|
|
// Inputs in [0, 2): exact enough for test data (truncating conversion).
|
|
uint32_t x;
|
|
std::memcpy(&x, &f, 4);
|
|
const uint32_t sign = (x >> 16) & 0x8000u;
|
|
const int exp = int((x >> 23) & 0xffu) - 127 + 15;
|
|
if (exp <= 0) {
|
|
return uint16_t(sign);
|
|
}
|
|
return uint16_t(sign | (uint32_t(exp) << 10) | ((x >> 13) & 0x3ffu));
|
|
}
|
|
|
|
struct Image {
|
|
VkImage image{};
|
|
VkImageView view{};
|
|
VkDeviceMemory memory{};
|
|
uint32_t width = 0, height = 0;
|
|
};
|
|
|
|
Image CreateImage(const Gpu& gpu, VkFormat format, uint32_t w, uint32_t h, VkImageUsageFlags usage,
|
|
VkImageAspectFlags aspect) {
|
|
Image image;
|
|
image.width = w;
|
|
image.height = h;
|
|
VkImageCreateInfo ci{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
|
ci.imageType = VK_IMAGE_TYPE_2D;
|
|
ci.format = format;
|
|
ci.extent = {w, h, 1};
|
|
ci.mipLevels = 1;
|
|
ci.arrayLayers = 1;
|
|
ci.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
ci.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
ci.usage = usage;
|
|
CHECK(vkCreateImage(gpu.device, &ci, nullptr, &image.image));
|
|
VkMemoryRequirements req;
|
|
vkGetImageMemoryRequirements(gpu.device, image.image, &req);
|
|
VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
|
ai.allocationSize = req.size;
|
|
ai.memoryTypeIndex = MemoryType(gpu, req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
CHECK(vkAllocateMemory(gpu.device, &ai, nullptr, &image.memory));
|
|
CHECK(vkBindImageMemory(gpu.device, image.image, image.memory, 0));
|
|
VkImageViewCreateInfo vi{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
|
vi.image = image.image;
|
|
vi.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
vi.format = format;
|
|
vi.subresourceRange = {aspect, 0, 1, 0, 1};
|
|
CHECK(vkCreateImageView(gpu.device, &vi, nullptr, &image.view));
|
|
return image;
|
|
}
|
|
|
|
FfxFsr4ModelPreset ModelPreset(int preset) {
|
|
switch (preset) {
|
|
case 0:
|
|
return FFX_FSR4_MODEL_PRESET_NATIVE_AA;
|
|
case 1:
|
|
return FFX_FSR4_MODEL_PRESET_QUALITY;
|
|
case 2:
|
|
return FFX_FSR4_MODEL_PRESET_BALANCED;
|
|
case 3:
|
|
return FFX_FSR4_MODEL_PRESET_PERFORMANCE;
|
|
default:
|
|
return FFX_FSR4_MODEL_PRESET_ULTRA_PERFORMANCE;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv) {
|
|
uint32_t rw = 2260, rh = 1272, ow = 3840, oh = 2160;
|
|
int preset = 2, frames = 900;
|
|
bool stats = false;
|
|
bool fsr411 = false;
|
|
int positional = 0;
|
|
for (int i = 1; i < argc; ++i) {
|
|
if (std::strcmp(argv[i], "--fsr411") == 0) {
|
|
fsr411 = true;
|
|
continue;
|
|
}
|
|
if (std::strcmp(argv[i], "--stats") == 0) {
|
|
stats = true;
|
|
continue;
|
|
}
|
|
switch (positional++) {
|
|
case 0:
|
|
std::sscanf(argv[i], "%ux%u", &rw, &rh);
|
|
break;
|
|
case 1:
|
|
std::sscanf(argv[i], "%ux%u", &ow, &oh);
|
|
break;
|
|
case 2:
|
|
preset = std::atoi(argv[i]);
|
|
break;
|
|
case 3:
|
|
frames = std::atoi(argv[i]);
|
|
break;
|
|
}
|
|
}
|
|
setenv("BB_FSR4_PROFILE", "1", 0);
|
|
if (stats) {
|
|
setenv("BB_FSR4_STATS", "1", 1);
|
|
}
|
|
const Gpu gpu = CreateGpu(stats);
|
|
|
|
FfxInterface backend{};
|
|
ffxContext context{};
|
|
std::vector<unsigned char> scratch;
|
|
if (!fsr411) {
|
|
// Assets, as vk_fsr4.cpp loads them.
|
|
FfxFsr4V07AssetSet assets{};
|
|
if (!ffxFsr4V07BuildAssetSet(ModelPreset(preset), ow, oh, &assets)) {
|
|
std::fprintf(stderr, "unsupported output size\n");
|
|
return 1;
|
|
}
|
|
const char* dir_env = std::getenv("BB_FSR4_DIR");
|
|
const std::string dir = std::string{dir_env && dir_env[0] ? dir_env : "fsr4_shaders"} + "/";
|
|
std::array<std::vector<unsigned char>, FFX_FSR4_VK_PASS_COUNT> code;
|
|
std::vector<unsigned char> initializer, weights;
|
|
// As vk_fsr4.cpp: passes from opt/ (tools/fsr4_optimize.sh) unless BB_FSR4_OPT=0.
|
|
const char* opt_env = std::getenv("BB_FSR4_OPT");
|
|
const bool use_opt = !(opt_env && opt_env[0] == '0');
|
|
int optimized = 0;
|
|
const auto load = [&](const char* name, std::vector<unsigned char>& data) {
|
|
if (use_opt && ReadFile(dir + "opt/" + name, data)) {
|
|
++optimized;
|
|
return;
|
|
}
|
|
if (!ReadFile(dir + name, data)) {
|
|
std::fprintf(stderr, "missing %s%s\n", dir.c_str(), name);
|
|
std::exit(1);
|
|
}
|
|
};
|
|
load(assets.pre, code[0]);
|
|
for (uint32_t pass = 0; pass < FFX_FSR4_MODEL_PASS_COUNT; ++pass) {
|
|
load(assets.model[pass], code[1 + pass]);
|
|
}
|
|
load(assets.post, code[13]);
|
|
load(assets.rcas, code[14]);
|
|
load(assets.spdAutoExposure, code[15]);
|
|
load(assets.initializer, initializer);
|
|
load(assets.prePassWeights, weights);
|
|
std::printf("%d passes from %sopt/\n", optimized, dir.c_str());
|
|
std::array<std::string, FFX_FSR4_VK_PASS_COUNT> entries;
|
|
entries.fill("main");
|
|
for (uint32_t pass = 1; pass <= FFX_FSR4_MODEL_PASS_COUNT; ++pass) {
|
|
entries[pass] = "fsr4_model_v07_i8_pass" + std::to_string(pass);
|
|
}
|
|
FfxFsr4VkCreateInfo ci{};
|
|
ci.device = gpu.device;
|
|
ci.physicalDevice = gpu.physical;
|
|
for (uint32_t i = 0; i < FFX_FSR4_VK_PASS_COUNT; ++i) {
|
|
ci.shaders[i] = {reinterpret_cast<const uint32_t*>(code[i].data()), code[i].size(),
|
|
entries[i].c_str()};
|
|
}
|
|
ci.modelInitializer = initializer.data();
|
|
ci.modelInitializerSize = initializer.size();
|
|
ci.prePassWeights = weights.data();
|
|
ci.prePassWeightsSize = weights.size();
|
|
scratch.assign(ffxFsr4VkGetScratchMemorySize(), 0);
|
|
ci.scratchBuffer = scratch.data();
|
|
ci.scratchBufferSize = scratch.size();
|
|
CHECK(ffxFsr4VkCreateContext(&ci, &backend));
|
|
|
|
ffxCreateContextDescUpscale desc{};
|
|
desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE;
|
|
desc.flags = FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE | FFX_UPSCALE_ENABLE_AUTO_EXPOSURE;
|
|
desc.maxRenderSize = {(ow + 7) & ~7u, (oh + 7) & ~7u};
|
|
desc.maxUpscaleSize = {(ow + 7) & ~7u, (oh + 7) & ~7u};
|
|
ffxFsr4V07SetBackendInterface(&backend);
|
|
if (ffxFsr4V07CreateContext(&context, &desc.header, nullptr) != FFX_API_RETURN_OK) {
|
|
std::fprintf(stderr, "provider context creation failed\n");
|
|
return 1;
|
|
}
|
|
ffxFsr4V07SetBackendInterface(nullptr);
|
|
std::printf("FSR 4 %s %ux%u -> %ux%u, %d frames\n",
|
|
ffxFsr4ModelPresetName(ModelPreset(preset)), rw, rh, ow, oh, frames);
|
|
}
|
|
|
|
const VkImageUsageFlags rw_usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT |
|
|
VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
Image color = CreateImage(gpu, VK_FORMAT_R16G16B16A16_SFLOAT, rw, rh, rw_usage,
|
|
VK_IMAGE_ASPECT_COLOR_BIT);
|
|
Image depth = CreateImage(gpu, VK_FORMAT_D32_SFLOAT, rw, rh,
|
|
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
|
VK_IMAGE_ASPECT_DEPTH_BIT);
|
|
Image motion = CreateImage(gpu, VK_FORMAT_R16G16_SFLOAT, rw, rh, rw_usage,
|
|
VK_IMAGE_ASPECT_COLOR_BIT);
|
|
Image output = CreateImage(gpu, VK_FORMAT_R16G16B16A16_SFLOAT, ow, oh, rw_usage,
|
|
VK_IMAGE_ASPECT_COLOR_BIT);
|
|
|
|
VkCommandPoolCreateInfo pci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
|
pci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
pci.queueFamilyIndex = gpu.family;
|
|
VkCommandPool pool;
|
|
CHECK(vkCreateCommandPool(gpu.device, &pci, nullptr, &pool));
|
|
VkCommandBufferAllocateInfo cai{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
|
cai.commandPool = pool;
|
|
cai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
cai.commandBufferCount = 1;
|
|
VkCommandBuffer cmd;
|
|
CHECK(vkAllocateCommandBuffers(gpu.device, &cai, &cmd));
|
|
VkFenceCreateInfo fci{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
|
VkFence fence;
|
|
CHECK(vkCreateFence(gpu.device, &fci, nullptr, &fence));
|
|
|
|
const auto submit = [&] {
|
|
CHECK(vkEndCommandBuffer(cmd));
|
|
VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
|
si.commandBufferCount = 1;
|
|
si.pCommandBuffers = &cmd;
|
|
CHECK(vkQueueSubmit(gpu.queue, 1, &si, fence));
|
|
CHECK(vkWaitForFences(gpu.device, 1, &fence, VK_TRUE, UINT64_MAX));
|
|
CHECK(vkResetFences(gpu.device, 1, &fence));
|
|
};
|
|
const VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
|
|
|
// Inputs: a mid-grey frame, far depth, small motion; all in General.
|
|
CHECK(vkBeginCommandBuffer(cmd, &begin));
|
|
const auto to_general = [&](const Image& image, VkImageAspectFlags aspect) {
|
|
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
|
b.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
b.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
b.image = image.image;
|
|
b.subresourceRange = {aspect, 0, 1, 0, 1};
|
|
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
|
|
};
|
|
to_general(color, VK_IMAGE_ASPECT_COLOR_BIT);
|
|
to_general(depth, VK_IMAGE_ASPECT_DEPTH_BIT);
|
|
to_general(motion, VK_IMAGE_ASPECT_COLOR_BIT);
|
|
to_general(output, VK_IMAGE_ASPECT_COLOR_BIT);
|
|
const VkImageSubresourceRange color_range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
const VkClearColorValue grey{{0.4f, 0.35f, 0.3f, 1.0f}}, mv{{0.25f, -0.5f, 0.0f, 0.0f}};
|
|
vkCmdClearColorImage(cmd, color.image, VK_IMAGE_LAYOUT_GENERAL, &grey, 1, &color_range);
|
|
vkCmdClearColorImage(cmd, motion.image, VK_IMAGE_LAYOUT_GENERAL, &mv, 1, &color_range);
|
|
const VkClearDepthStencilValue far{0.5f, 0};
|
|
const VkImageSubresourceRange depth_range{VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
|
|
vkCmdClearDepthStencilImage(cmd, depth.image, VK_IMAGE_LAYOUT_GENERAL, &far, 1, &depth_range);
|
|
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
|
mb.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
mb.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
|
0, 1, &mb, 0, nullptr, 0, nullptr);
|
|
submit();
|
|
|
|
const char* noise_env = std::getenv("BENCH_NOISE");
|
|
if (noise_env && noise_env[0] == '1') {
|
|
// Pseudo-random color, motion within +-2 pixels, depth; one upload.
|
|
const VkDeviceSize color_bytes = VkDeviceSize(rw) * rh * 8, motion_bytes = VkDeviceSize(rw) * rh * 4,
|
|
depth_bytes = VkDeviceSize(rw) * rh * 4;
|
|
Buffer staging = CreateHostBuffer(gpu, color_bytes + motion_bytes + depth_bytes,
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
|
uint32_t seed = 12345u;
|
|
const auto next = [&] {
|
|
seed = seed * 1664525u + 1013904223u;
|
|
return float(seed >> 8) / float(1u << 24);
|
|
};
|
|
auto* bytes = static_cast<unsigned char*>(staging.data);
|
|
auto* c16 = reinterpret_cast<uint16_t*>(bytes);
|
|
for (VkDeviceSize i = 0; i < VkDeviceSize(rw) * rh; ++i) {
|
|
c16[i * 4 + 0] = Half(next() * 1.5f);
|
|
c16[i * 4 + 1] = Half(next() * 1.5f);
|
|
c16[i * 4 + 2] = Half(next() * 1.5f);
|
|
c16[i * 4 + 3] = Half(1.0f);
|
|
}
|
|
auto* m16 = reinterpret_cast<uint16_t*>(bytes + color_bytes);
|
|
for (VkDeviceSize i = 0; i < VkDeviceSize(rw) * rh * 2; ++i) {
|
|
const float v = next() * 4.0f - 2.0f;
|
|
m16[i] = Half(v);
|
|
}
|
|
auto* d32 = reinterpret_cast<float*>(bytes + color_bytes + motion_bytes);
|
|
for (VkDeviceSize i = 0; i < VkDeviceSize(rw) * rh; ++i) {
|
|
d32[i] = 0.9f + next() * 0.1f;
|
|
}
|
|
CHECK(vkBeginCommandBuffer(cmd, &begin));
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
|
region.imageExtent = {rw, rh, 1};
|
|
vkCmdCopyBufferToImage(cmd, staging.buffer, color.image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion);
|
|
region.bufferOffset = color_bytes;
|
|
vkCmdCopyBufferToImage(cmd, staging.buffer, motion.image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion);
|
|
region.bufferOffset = color_bytes + motion_bytes;
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
vkCmdCopyBufferToImage(cmd, staging.buffer, depth.image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion);
|
|
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &mb, 0, nullptr, 0,
|
|
nullptr);
|
|
submit();
|
|
}
|
|
|
|
const auto resource = [](const Image& image, uint32_t format, uint32_t state) {
|
|
FfxApiResource r{};
|
|
r.resource = reinterpret_cast<void*>(image.view);
|
|
r.description.type = FFX_RESOURCE_TYPE_TEXTURE2D;
|
|
r.description.format = format;
|
|
r.description.width = image.width;
|
|
r.description.height = image.height;
|
|
r.description.depth = 1;
|
|
r.description.mipCount = 1;
|
|
r.state = state;
|
|
return r;
|
|
};
|
|
const auto register_image = [&](const Image& image, VkAccessFlags access) {
|
|
const FfxFsr4VkExternalImageState state{
|
|
sizeof(FfxFsr4VkExternalImageState), image.image, image.view,
|
|
VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, access,
|
|
VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, access};
|
|
CHECK(ffxFsr4VkSetExternalImageState(&backend, &state));
|
|
};
|
|
std::unique_ptr<Fsr411::Upscaler> upscaler411;
|
|
VkQueryPool timestamps = VK_NULL_HANDLE;
|
|
double gpu_ms = 0.0;
|
|
float period_ns = 1.0f;
|
|
if (fsr411) {
|
|
const char* dir411 = std::getenv("BB_FSR411_DIR");
|
|
upscaler411 = std::make_unique<Fsr411::Upscaler>(gpu.physical, gpu.device,
|
|
dir411 && dir411[0] ? dir411 : "fsr4_411", gpu.fsr411);
|
|
VkQueryPoolCreateInfo qci{VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO};
|
|
qci.queryType = VK_QUERY_TYPE_TIMESTAMP;
|
|
qci.queryCount = 2;
|
|
CHECK(vkCreateQueryPool(gpu.device, &qci, nullptr, ×tamps));
|
|
VkPhysicalDeviceProperties props;
|
|
vkGetPhysicalDeviceProperties(gpu.physical, &props);
|
|
period_ns = props.limits.timestampPeriod;
|
|
std::printf("FSR 4.1.1 replay %ux%u -> %ux%u, %d frames\n", rw, rh, ow, oh, frames);
|
|
}
|
|
for (int frame = 1; fsr411 && frame <= frames; ++frame) {
|
|
CHECK(vkBeginCommandBuffer(cmd, &begin));
|
|
vkCmdResetQueryPool(cmd, timestamps, 0, 2);
|
|
vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, timestamps, 0);
|
|
Fsr411::Frame f;
|
|
f.cmdbuf = cmd;
|
|
f.color = {color.image, color.view, rw, rh};
|
|
f.depth = {depth.image, depth.view, rw, rh};
|
|
f.motion = {motion.image, motion.view, rw, rh};
|
|
f.output = {output.image, output.view, ow, oh};
|
|
f.render_width = rw;
|
|
f.render_height = rh;
|
|
f.ultra_performance = preset >= 4;
|
|
f.jitter[0] = 0.25f * float((frame - 1) % 4) - 0.375f; // as fsr4cap
|
|
f.jitter[1] = 0.125f;
|
|
f.sharpen = true;
|
|
f.sharpness = 0.5f;
|
|
f.reset = frame == 1;
|
|
f.auto_exposure = true;
|
|
if (!upscaler411->Record(f)) {
|
|
std::fprintf(stderr, "FSR 4.1.1: %s\n", upscaler411->Error().c_str());
|
|
return 1;
|
|
}
|
|
if (frame == 1) {
|
|
std::printf("FSR 4.1.1: %s\n", upscaler411->Describe().c_str());
|
|
}
|
|
vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, timestamps, 1);
|
|
submit();
|
|
uint64_t ts[2];
|
|
CHECK(vkGetQueryPoolResults(gpu.device, timestamps, 0, 2, sizeof(ts), ts, 8, VK_QUERY_RESULT_64_BIT));
|
|
gpu_ms += double(ts[1] - ts[0]) * period_ns * 1e-6;
|
|
if (frame % 300 == 0) {
|
|
std::printf("FSR 4.1.1 replay: %.3f ms/frame (%s)\n", gpu_ms / 300.0, upscaler411->Describe().c_str());
|
|
gpu_ms = 0.0;
|
|
}
|
|
}
|
|
for (int frame = 1; !fsr411 && frame <= frames; ++frame) {
|
|
CHECK(ffxFsr4VkBeginFrame(&backend, uint64_t(frame)));
|
|
CHECK(vkBeginCommandBuffer(cmd, &begin));
|
|
register_image(color, VK_ACCESS_SHADER_READ_BIT);
|
|
register_image(depth, VK_ACCESS_SHADER_READ_BIT);
|
|
register_image(motion, VK_ACCESS_SHADER_READ_BIT);
|
|
register_image(output, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
|
ffxDispatchDescUpscale d{};
|
|
d.header.type = FFX_API_DISPATCH_DESC_TYPE_UPSCALE;
|
|
d.commandList = cmd;
|
|
d.color = resource(color, FFX_SURFACE_FORMAT_R16G16B16A16_FLOAT,
|
|
FFX_API_RESOURCE_STATE_COMPUTE_READ);
|
|
d.depth = resource(depth, FFX_SURFACE_FORMAT_R32_FLOAT, FFX_API_RESOURCE_STATE_COMPUTE_READ);
|
|
d.motionVectors = resource(motion, FFX_SURFACE_FORMAT_R16G16_FLOAT,
|
|
FFX_API_RESOURCE_STATE_COMPUTE_READ);
|
|
d.output = resource(output, FFX_SURFACE_FORMAT_R16G16B16A16_FLOAT,
|
|
FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
|
|
d.jitterOffset = {0.25f * float(frame % 4) - 0.375f, 0.125f};
|
|
d.motionVectorScale = {1.0f, 1.0f};
|
|
d.renderSize = {rw, rh};
|
|
d.upscaleSize = {ow, oh};
|
|
d.enableSharpening = true;
|
|
d.sharpness = 0.5f;
|
|
d.enableAutoExposure = true;
|
|
d.frameTimeDelta = 10.0f;
|
|
d.preExposure = 1.0f;
|
|
d.reset = frame == 1;
|
|
d.cameraNear = 0.05f;
|
|
d.cameraFar = 3000.0f;
|
|
d.cameraFovAngleVertical = 0.75f;
|
|
d.viewSpaceToMetersFactor = 1.0f;
|
|
if (ffxFsr4V07Dispatch(&context, &d.header) != FFX_API_RETURN_OK) {
|
|
std::fprintf(stderr, "dispatch failed\n");
|
|
return 1;
|
|
}
|
|
submit();
|
|
CHECK(ffxFsr4VkRetireFrame(&backend, uint64_t(frame)));
|
|
}
|
|
CHECK(vkDeviceWaitIdle(gpu.device));
|
|
if (const char* dump = std::getenv("BENCH_DUMP")) {
|
|
const VkDeviceSize bytes = VkDeviceSize(ow) * oh * 8;
|
|
Buffer readback = CreateHostBuffer(gpu, bytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
|
CHECK(vkBeginCommandBuffer(cmd, &begin));
|
|
VkMemoryBarrier rb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
|
rb.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
|
rb.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &rb, 0, nullptr, 0, nullptr);
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
|
region.imageExtent = {ow, oh, 1};
|
|
vkCmdCopyImageToBuffer(cmd, output.image, VK_IMAGE_LAYOUT_GENERAL, readback.buffer, 1,
|
|
®ion);
|
|
submit();
|
|
if (FILE* f = std::fopen(dump, "wb")) {
|
|
std::fwrite(readback.data, 1, size_t(bytes), f);
|
|
std::fclose(f);
|
|
}
|
|
}
|
|
upscaler411.reset();
|
|
if (!fsr411) {
|
|
ffxFsr4V07DestroyContext(&context, nullptr);
|
|
ffxFsr4VkDestroyContext(reinterpret_cast<FfxFsr4VkContext*>(scratch.data()));
|
|
}
|
|
return 0;
|
|
}
|